diff --git a/Cargo.lock b/Cargo.lock index 99b1f76..9edca23 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5782,7 +5782,7 @@ dependencies = [ [[package]] name = "synapse" -version = "1.11.0" +version = "1.12.0" dependencies = [ "anyhow", "async-channel 2.5.0", @@ -5797,7 +5797,7 @@ dependencies = [ [[package]] name = "synapsecore" -version = "1.11.0" +version = "1.12.0" dependencies = [ "anyhow", "chacha20poly1305", diff --git a/README.md b/README.md index e2d779e..5d90dac 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ synapse connect synapse ``` -`synapse connect` connects installed tools. Restart them afterward. Use `synapse status` for text status; the same binary provides the CLI and MCP server. The Linux build has no desktop UI dependency and uses the encrypted vault instead of macOS Keychain. Current release downloads contain the macOS app; Linux users build from source. +`synapse connect` connects installed tools. Restart them afterward. Use `synapse status` for text status; the same binary provides the CLI and MCP server. The Linux build has no desktop UI dependency and uses the encrypted vault instead of macOS Keychain. Release downloads contain the macOS app and Windows installer; Linux users build from source. See the [installation guide](https://wess.io/synapse/docs/install/) for CLI setup and connection troubleshooting. diff --git a/crates/synapse/Cargo.toml b/crates/synapse/Cargo.toml index d2f1b72..a1eb9c7 100644 --- a/crates/synapse/Cargo.toml +++ b/crates/synapse/Cargo.toml @@ -2,7 +2,7 @@ name = "synapse" # Bumping this on main is the release trigger. Keep it in step with # crates/synapsecore, which is where `synapse version` reads its number from. -version = "1.11.0" +version = "1.12.0" edition.workspace = true description = "Local memory and scoped credentials for developer tools" diff --git a/crates/synapse/src/ui/savings.rs b/crates/synapse/src/ui/savings.rs index 22da438..433eb7c 100644 --- a/crates/synapse/src/ui/savings.rs +++ b/crates/synapse/src/ui/savings.rs @@ -424,8 +424,9 @@ fn empty(border: Hsla, surface: Hsla) -> AnyElement { fn footnote(report: &Report) -> AnyElement { let since = report.since.as_deref().unwrap_or_default(); Text::new(format!( - "Counting since {since} on this machine. Tokens are characters ÷ {}, counted once per session, so treat these as a floor.", - usage::PERTOKEN + "Counting since {since} on this machine. Usage older than {} days is pruned. Tokens are characters ÷ {}, counted once per session, so treat these as a floor.", + usage::RETENTIONDAYS, + usage::PERTOKEN, )) .size(Size::Xs) .dimmed() diff --git a/crates/synapsecore/Cargo.lock b/crates/synapsecore/Cargo.lock index 026ad58..3c9c183 100644 --- a/crates/synapsecore/Cargo.lock +++ b/crates/synapsecore/Cargo.lock @@ -1789,7 +1789,7 @@ dependencies = [ [[package]] name = "synapsecore" -version = "1.11.0" +version = "1.12.0" dependencies = [ "anyhow", "chacha20poly1305", diff --git a/crates/synapsecore/Cargo.toml b/crates/synapsecore/Cargo.toml index f316abe..1e93278 100644 --- a/crates/synapsecore/Cargo.toml +++ b/crates/synapsecore/Cargo.toml @@ -20,7 +20,7 @@ name = "synapsecore" # Keep this in step with crates/synapse: `synapse version` reads the version of # the crate that holds the command, and the two ship as one product. Only the # desktop crate's version triggers the release workflow. -version = "1.11.0" +version = "1.12.0" edition = "2024" description = "Memory, credentials, and the command surface for Synapse, without a window" diff --git a/crates/synapsecore/examples/retrieval.rs b/crates/synapsecore/examples/retrieval.rs index acf8658..5840571 100644 --- a/crates/synapsecore/examples/retrieval.rs +++ b/crates/synapsecore/examples/retrieval.rs @@ -5,6 +5,7 @@ use serde_json::{Value, json}; use std::future::Future; use std::time::Instant; use synapsecore::brain::{Brain, Optimization, RecallResponse}; +use synapsecore::usage::{self, Entry}; #[tokio::main] async fn main() -> Result<()> { @@ -56,6 +57,47 @@ async fn main() -> Result<()> { .await?, ); } + measurements.push( + measure("recall_with_drift", iterations, || async { + let (settings, memories) = brain + .recallscoped( + "database migrations scheduling durability", + 4, + Some(Optimization::Lean), + None, + ) + .await?; + let drift = brain.drifted(&memories, None).await?; + let response = RecallResponse { + optimization: settings.optimization, + memories: memories.into_iter().map(Into::into).collect(), + drift, + }; + Ok(serde_json::to_vec(&response)?.len()) + }) + .await?, + ); + let (_, sample) = brain + .recallscoped( + "database migrations scheduling durability", + 4, + Some(Optimization::Lean), + None, + ) + .await?; + measurements.push( + measure("drift_only", iterations, || async { + Ok(serde_json::to_vec(&brain.drifted(&sample, None).await?)?.len()) + }) + .await?, + ); + measurements.push( + measure("usage_record", iterations, || async { + usage::record(&brain, Entry::session(100_000, 2_000)).await?; + Ok(0) + }) + .await?, + ); measurements.push( measure("readmemory", iterations, || async { let response = brain diff --git a/crates/synapsecore/src/brain/drift.rs b/crates/synapsecore/src/brain/drift.rs index a6b3877..098afa4 100644 --- a/crates/synapsecore/src/brain/drift.rs +++ b/crates/synapsecore/src/brain/drift.rs @@ -27,10 +27,18 @@ use std::path::{Path, PathBuf}; /// Newest memories read by a full check. pub const SCANCEILING: i64 = 5_000; +/// Maximum pair checks and findings in one advisory scan. +const PAIRCEILING: usize = 50_000; +const PROBECEILING: usize = 200_000; +const FINDINGCEILING: usize = 1_000; + /// Candidates read when looking for one memory's counterparts. A contradiction /// shares most of its words, so it ranks near the top. const COUNTERPARTS: i64 = 8; +/// Recent visible memories checked for a correction the query did not match. +const RECENT: i64 = 256; + /// Words from the memory used for that search. const PROBE: usize = 16; @@ -67,6 +75,8 @@ pub struct Drift { pub scanned: usize, /// More than [`SCANCEILING`] memories, so only the newest were read. pub capped: bool, + /// A dense set of similar memories hit the pair or finding safety limit. + pub partial: bool, pub findings: Vec, } @@ -85,12 +95,14 @@ impl Brain { .context("could not read memory to check")?; let capped = memories.len() as i64 > SCANCEILING; let memories = &memories[..memories.len().min(SCANCEILING as usize)]; + let (findings, partial) = scanlimited(memories, &checkout); Ok(Drift { root: checkout.root().map(Path::to_path_buf), project, scanned: memories.len(), capped, - findings: scan(memories, &checkout), + partial, + findings, }) } } @@ -111,13 +123,44 @@ impl Brain { .flatten() .unwrap_or_default(); let mut findings = scan(memories, &checkout); + if memories.is_empty() { + return Ok(findings); + } let present: HashSet = memories.iter().map(|memory| memory.id).collect(); + let earliest = memories + .iter() + .map(|memory| memory.created) + .min() + .unwrap_or_default(); + let recent = sqlx::query_as::<_, Memory>(&format!( + "SELECT {COLUMNS} FROM memorymeta meta JOIN memory ON memory.rowid = meta.memoryid \ + WHERE {VISIBLE} AND meta.created >= ? \ + ORDER BY meta.created DESC, meta.memoryid DESC LIMIT ?" + )) + .bind(&project) + .bind(earliest) + .bind(RECENT) + .fetch_all(&self.pool) + .await + .context("could not check recent memory for corrections")?; + let profiles: Vec = recent + .iter() + .map(|other| compare::profile(&other.body)) + .collect(); for memory in memories { - for (other, relation) in self.counterparts(memory, &project).await? { - let newer = (other.created, other.id) > (memory.created, memory.id); - if present.contains(&other.id) || !newer { + let own = compare::profile(&memory.body); + if !own.comparable() { + continue; + } + for (other, profile) in recent.iter().zip(&profiles) { + if present.contains(&other.id) + || (other.created, other.id) <= (memory.created, memory.id) + { continue; } + let Some(relation @ Relation::Conflict(_)) = compare::relate(&own, profile) else { + continue; + }; let mut finding = paired(memory, &other, relation); finding.note.push_str(&format!( " #{} is not in this response; it says: {}", @@ -127,6 +170,7 @@ impl Brain { if !findings.iter().any(|known| known.ids == finding.ids) { findings.push(finding); } + break; } } findings.sort_by(|a, b| a.concern.cmp(&b.concern).then(a.ids.cmp(&b.ids))); @@ -205,21 +249,36 @@ fn quoted(body: &str) -> String { } pub fn scan(memories: &[Memory], checkout: &Checkout) -> Vec { + scanlimited(memories, checkout).0 +} + +fn scanlimited(memories: &[Memory], checkout: &Checkout) -> (Vec, bool) { let mut findings: Vec = memories .iter() .filter_map(|memory| stale(memory, checkout)) + .take(FINDINGCEILING + 1) .collect(); + if findings.len() > FINDINGCEILING { + findings.truncate(FINDINGCEILING); + return (findings, true); + } let profiles: Vec = memories .iter() .map(|memory| compare::profile(&memory.body)) .collect(); - for (first, second) in candidates(&profiles) { + let (pairs, pairscapped) = candidates(&profiles); + let mut findingscapped = false; + for (first, second) in pairs { if let Some(relation) = compare::relate(&profiles[first], &profiles[second]) { findings.push(paired(&memories[first], &memories[second], relation)); + if findings.len() == FINDINGCEILING { + findingscapped = true; + break; + } } } findings.sort_by(|a, b| a.concern.cmp(&b.concern).then(a.ids.cmp(&b.ids))); - findings + (findings, pairscapped || findingscapped) } fn stale(memory: &Memory, checkout: &Checkout) -> Option { @@ -253,7 +312,7 @@ fn stale(memory: &Memory, checkout: &Checkout) -> Option { /// share one within the first `size - α + 1` of each. `relate` never reports a /// pair with fewer than [`compare::MINSHARED`] shared words, so indexing just /// those prefixes misses nothing, and rare words keep the posting lists short. -fn candidates(profiles: &[Profile]) -> Vec<(usize, usize)> { +fn candidates(profiles: &[Profile]) -> (Vec<(usize, usize)>, bool) { let mut frequency: HashMap<&String, usize> = HashMap::new(); for profile in profiles.iter().filter(|profile| profile.comparable()) { for word in profile.words() { @@ -272,17 +331,26 @@ fn candidates(profiles: &[Profile]) -> Vec<(usize, usize)> { postings.entry(word).or_default().push(index); } } - let mut pairs: Vec<(usize, usize)> = Vec::new(); - for holders in postings.values() { + let mut ordered: Vec<_> = postings.into_iter().collect(); + ordered.sort_by(|(a, ah), (b, bh)| ah.len().cmp(&bh.len()).then(a.cmp(b))); + let mut pairs: HashSet<(usize, usize)> = HashSet::new(); + let mut probes = 0; + for (_, holders) in ordered { for (position, first) in holders.iter().enumerate() { for second in &holders[position + 1..] { - pairs.push((*first.min(second), *first.max(second))); + probes += 1; + pairs.insert((*first.min(second), *first.max(second))); + if pairs.len() == PAIRCEILING || probes == PROBECEILING { + let mut pairs: Vec<_> = pairs.into_iter().collect(); + pairs.sort_unstable(); + return (pairs, true); + } } } } + let mut pairs: Vec<_> = pairs.into_iter().collect(); pairs.sort_unstable(); - pairs.dedup(); - pairs + (pairs, false) } fn paired(first: &Memory, second: &Memory, relation: Relation) -> Finding { diff --git a/crates/synapsecore/src/brain/drift/checkout.rs b/crates/synapsecore/src/brain/drift/checkout.rs index 7d6fed8..2e70e09 100644 --- a/crates/synapsecore/src/brain/drift/checkout.rs +++ b/crates/synapsecore/src/brain/drift/checkout.rs @@ -83,7 +83,10 @@ impl Checkout { /// pass true for memory from this project. pub fn presence(&self, reference: &str, relative: bool) -> Presence { // Build output comes and goes; an unbuilt binary isn't a stale memory. - if reference.split('/').any(|part| SKIPPED.contains(&part)) { + if reference + .split(['/', '\\']) + .any(|part| SKIPPED.contains(&part)) + { return Presence::Unknown; } if let Some(rest) = reference.strip_prefix("~/") { @@ -92,13 +95,14 @@ impl Checkout { None => Presence::Unknown, }; } - if reference.starts_with('/') { - return self.absolute(Path::new(reference)); + let path = Path::new(reference); + if path.is_absolute() { + return self.absolute(path); } let Some(root) = self.root.as_deref().filter(|_| relative) else { return Presence::Unknown; }; - let wanted = reference.trim_end_matches('/'); + let wanted = reference.trim_end_matches(['/', '\\']); if root.join(wanted).exists() { return Presence::Present; } @@ -106,8 +110,9 @@ impl Checkout { // `src/cli/memory.rs` for `crates/core/src/cli/memory.rs`. match self.index.get_or_init(|| walk(root)) { Some(index) => { + let wanted = wanted.replace('\\', "/"); let suffix = format!("/{wanted}"); - match index.contains(wanted) || index.iter().any(|path| path.ends_with(&suffix)) { + match index.contains(&wanted) || index.iter().any(|path| path.ends_with(&suffix)) { true => Presence::Present, false => Presence::Missing, } @@ -126,7 +131,16 @@ impl Checkout { .is_some_and(|home| path.starts_with(home)); let foreign = ["/Users/", "/home/"] .iter() - .any(|prefix| text.starts_with(prefix) && !own); + .any(|prefix| text.starts_with(prefix) && !own) + || self.home.as_deref().is_some_and(|home| { + home.parent().is_some_and(|parent| { + parent + .file_name() + .is_some_and(|name| name.eq_ignore_ascii_case("users") || name == "home") + && path.starts_with(parent) + && !own + }) + }); let transient = !own && TRANSIENT.iter().any(|prefix| text.starts_with(prefix)); match foreign || transient { true => Presence::Unknown, @@ -282,8 +296,28 @@ mod tests { checkout.presence(&own.to_string_lossy(), false), Presence::Present ); + #[cfg(windows)] + let missing = std::env::current_dir() + .unwrap() + .join("definitely-not-present.rs"); + #[cfg(not(windows))] + let missing = PathBuf::from("/definitely/not/here.rs"); assert_eq!( - checkout.presence("/definitely/not/here.rs", false), + checkout.presence(&missing.to_string_lossy(), false), + Presence::Missing + ); + } + + #[cfg(windows)] + #[test] + fn windows_absolute_paths_and_other_accounts_home() { + let checkout = Checkout::new(None, None, Some(PathBuf::from("C:\\Users\\me"))); + assert_eq!( + checkout.presence("C:\\Users\\somebodyelse\\project\\a.rs", false), + Presence::Unknown + ); + assert_eq!( + checkout.presence("C:\\definitely\\not\\here.rs", false), Presence::Missing ); } diff --git a/crates/synapsecore/src/cli/doctor.rs b/crates/synapsecore/src/cli/doctor.rs index 03826d2..56d81ab 100644 --- a/crates/synapsecore/src/cli/doctor.rs +++ b/crates/synapsecore/src/cli/doctor.rs @@ -67,13 +67,15 @@ pub struct Drifted { pub stale: usize, pub conflicting: usize, pub repeated: usize, + pub limited: bool, } impl Drifted { - fn of(findings: &[crate::brain::drift::Finding]) -> Self { + fn of(drift: &crate::brain::drift::Drift) -> Self { use crate::brain::drift::Concern; let count = |concern: Concern| { - findings + drift + .findings .iter() .filter(|finding| finding.concern == concern) .count() @@ -82,6 +84,7 @@ impl Drifted { stale: count(Concern::Stale), conflicting: count(Concern::Conflict), repeated: count(Concern::Duplicate), + limited: drift.capped || drift.partial, } } @@ -89,9 +92,15 @@ impl Drifted { if *self == Self::default() { return "none found from this folder".to_owned(); } + if self.stale + self.conflicting + self.repeated == 0 { + return "none found in the checked portion (see synapse memory drift)".to_owned(); + } format!( - "{} stale, {} conflicting, {} repeated (see synapse memory drift)", - self.stale, self.conflicting, self.repeated + "{} stale, {} conflicting, {} repeated{} (see synapse memory drift)", + self.stale, + self.conflicting, + self.repeated, + if self.limited { "; check limited" } else { "" } ) } } @@ -340,7 +349,7 @@ fn store(database: Option<&Path>) -> Store { .drift(folder.as_deref()) .await .ok() - .map(|drift| Drifted::of(&drift.findings)); + .map(|drift| Drifted::of(&drift)); Ok::<_, anyhow::Error>(( report.version, brain.stats().await?.entries, @@ -558,7 +567,7 @@ fn runtime() -> Result { #[cfg(test)] mod tests { use super::*; - use crate::brain::drift::{Concern, Finding}; + use crate::brain::drift::{Concern, Drift, Finding}; #[test] fn drift_is_counted_by_kind_and_says_where_to_look() { @@ -568,15 +577,30 @@ mod tests { note: String::new(), missing: Vec::new(), }; - let drifted = Drifted::of(&[ - finding(Concern::Stale), - finding(Concern::Conflict), - finding(Concern::Conflict), - ]); + let drift = Drift { + root: None, + project: None, + scanned: 3, + capped: false, + partial: false, + findings: vec![ + finding(Concern::Stale), + finding(Concern::Conflict), + finding(Concern::Conflict), + ], + }; + let drifted = Drifted::of(&drift); assert_eq!( drifted.line(), "1 stale, 2 conflicting, 0 repeated (see synapse memory drift)" ); - assert_eq!(Drifted::of(&[]).line(), "none found from this folder"); + assert_eq!( + Drifted::of(&Drift { + findings: Vec::new(), + ..drift + }) + .line(), + "none found from this folder" + ); } } diff --git a/crates/synapsecore/src/cli/drift.rs b/crates/synapsecore/src/cli/drift.rs index ee01228..544abca 100644 --- a/crates/synapsecore/src/cli/drift.rs +++ b/crates/synapsecore/src/cli/drift.rs @@ -81,6 +81,11 @@ fn report(drift: &Drift) -> String { thousands(SCANCEILING as usize) )); } + if drift.partial { + text.push_str( + "Similar-memory comparisons hit a resource limit; findings may be incomplete.\n", + ); + } if drift.root.is_none() { text.push_str( "No checkout here, so paths relative to a project were not checked. \ @@ -131,6 +136,7 @@ mod tests { project: Some("github.com/acme/app".into()), scanned: 12, capped: false, + partial: false, findings: vec![ finding( Concern::Stale, @@ -167,6 +173,7 @@ mod tests { project: None, scanned: 1, capped: true, + partial: false, findings: Vec::new(), }; let text = report(&drift); diff --git a/crates/synapsecore/src/cli/savings.rs b/crates/synapsecore/src/cli/savings.rs index 452aa52..a37d8f4 100644 --- a/crates/synapsecore/src/cli/savings.rs +++ b/crates/synapsecore/src/cli/savings.rs @@ -115,7 +115,10 @@ fn print(report: &Report) { } println!(); if let Some(since) = &report.since { - println!(" Counting since {since} on this machine."); + println!( + " Counting since {since} on this machine. Usage older than {} days is pruned.", + crate::usage::RETENTIONDAYS + ); } println!( " Baseline: the same memory in an instruction file every session loads.\n \ diff --git a/crates/synapsecore/src/cli/session.rs b/crates/synapsecore/src/cli/session.rs index ac8d073..e5e02b0 100644 --- a/crates/synapsecore/src/cli/session.rs +++ b/crates/synapsecore/src/cli/session.rs @@ -25,7 +25,7 @@ //! `statusline` answers Claude Code's `statusLine` command and prints the one //! line under the prompt for the rest of the session. -use crate::brain::drift::{Checkout, Finding}; +use crate::brain::drift::{Checkout, Concern, Finding}; use crate::brain::{Brain, Memory, Optimization}; use crate::cli::Outcome; use crate::usage::Entry; @@ -62,6 +62,9 @@ pub struct Report { /// Why memories were left out of the hand-over. #[serde(skip_serializing_if = "Vec::is_empty")] pub held: Vec, + /// Possible conflicts and duplicates need human judgment, so both stay visible. + #[serde(skip_serializing_if = "Vec::is_empty")] + pub flagged: Vec, /// Set instead of the rest when Synapse could not be read at all. pub problem: Option, } @@ -266,6 +269,7 @@ fn context(report: &Report) -> String { ); context.push_str(&recalled(report)); context.push_str(&withheld(report)); + context.push_str(&flagged(report)); context } @@ -287,11 +291,26 @@ fn withheld(report: &Report) -> String { .iter() .map(|finding| format!("- {}\n", finding.note)) .collect(); + let count = held(report); + let noun = if count == 1 { "memory" } else { "memories" }; format!( - "\n\nSynapse held back {} more because they look stale or a newer memory above \ - contradicts or repeats them:\n\n{notes}\nPrefer the memories above. `recall` still \ - returns the held-back ones, marked, if the task needs them.", - held(report) + "\n\nSynapse held back {count} {noun} naming missing paths:\n\n{notes}\n`recall` still \ + returns them, marked, if the task needs them.", + ) +} + +fn flagged(report: &Report) -> String { + if report.flagged.is_empty() { + return String::new(); + } + let notes: String = report + .flagged + .iter() + .map(|finding| format!("- {}\n", finding.note)) + .collect(); + format!( + "\n\nSynapse found possible conflicts or repeats among the memories above. \ + Both sides remain visible because the check cannot prove they refer to the same context:\n\n{notes}" ) } @@ -312,9 +331,10 @@ fn restored(report: &Report) -> String { format!( "This session was just compacted, which dropped the memory Synapse handed over when it \ started. Here it is again, most recent first, including anything remembered earlier in \ - this session:\n\n{}{}", + this session:\n\n{}{}{}", recalled(report), - withheld(report) + withheld(report), + flagged(report) ) } @@ -407,6 +427,7 @@ fn collect(root: Option<&Path>, recall: Recall) -> Report { vault: "inactive".to_owned(), recalled: Vec::new(), held: Vec::new(), + flagged: Vec::new(), problem: Some(shortened(&format!("{error:#}"))), }, } @@ -463,15 +484,15 @@ fn gather(root: Option<&Path>, recall: Recall) -> Result { // An empty query asks for nothing in particular, which is exactly the // question a session opening has: it routes to the recent list, scoped // to global memory plus this project's, ordered newest first. - let (recalled, held, matched) = match recall { + let (recalled, held, flagged, matched) = match recall { Recall::Yes(_) => { let (_, memories, matched) = brain .recallmeasured("", u32::MAX, Some(BUDGET), root) .await?; - let (recalled, held) = screened(memories, &Checkout::at(root)); - (recalled, held, matched) + let (recalled, held, flagged) = screened(memories, &Checkout::at(root)); + (recalled, held, flagged, matched) } - Recall::No => (Vec::new(), Vec::new(), 0), + Recall::No => (Vec::new(), Vec::new(), Vec::new(), 0), }; let report = Report { connected: true, @@ -484,6 +505,7 @@ fn gather(root: Option<&Path>, recall: Recall) -> Result { vault: vault.to_owned(), recalled, held, + flagged, problem: None, }; if let Recall::Yes(handed) = recall { @@ -493,21 +515,27 @@ fn gather(root: Option<&Path>, recall: Recall) -> Result { }) } -/// Drops stale memories and the older half of any conflicting or duplicate -/// pair from the session hand-over. It's injected unasked into every session, -/// so it should only carry what still holds. `recall` still returns them. -fn screened(memories: Vec, checkout: &Checkout) -> (Vec, Vec) { +/// Missing paths can be checked here. Pair matches are only lexical guesses, +/// so both memories stay in the hand-over with a warning. +fn screened( + memories: Vec, + checkout: &Checkout, +) -> (Vec, Vec, Vec) { let findings = crate::brain::drift::scan(&memories, checkout); - // Ids are oldest first: the stale memory, or the older of a pair. - let withheld: std::collections::HashSet = findings + let (held, mut flagged): (Vec, Vec) = findings + .iter() + .cloned() + .partition(|finding| finding.concern == Concern::Stale); + let withheld: std::collections::HashSet = held .iter() .filter_map(|finding| finding.ids.first().copied()) .collect(); + flagged.retain(|finding| finding.ids.iter().all(|id| !withheld.contains(id))); let kept = memories .into_iter() .filter(|memory| !withheld.contains(&memory.id)) .collect(); - (kept, findings) + (kept, held, flagged) } /// Records the session in the savings ledger. Errors are ignored so the hook @@ -602,6 +630,7 @@ mod tests { vault: "inactive".to_owned(), recalled: Vec::new(), held: Vec::new(), + flagged: Vec::new(), problem: None, } } @@ -809,7 +838,7 @@ mod tests { } #[test] - fn a_session_is_not_handed_what_drift_has_flagged() { + fn a_session_holds_missing_paths_but_keeps_both_sides_of_a_possible_conflict() { let directory = tempfile::tempdir().unwrap(); let root = directory.path().join("app"); std::fs::create_dir_all(root.join(".git")).unwrap(); @@ -843,11 +872,31 @@ mod tests { 10, )), ]; - let (kept, held) = screened(memories, &Checkout::at(Some(&root))); + let (kept, held, flagged) = screened(memories, &Checkout::at(Some(&root))); let ids: Vec = kept.iter().map(|memory| memory.id).collect(); - assert_eq!(ids, vec![4, 2]); - let flagged: Vec> = held.iter().map(|finding| finding.ids.clone()).collect(); - assert_eq!(flagged, vec![vec![3], vec![1, 4]]); + assert_eq!(ids, vec![4, 2, 1]); + assert_eq!(held[0].ids, vec![3]); + assert_eq!(flagged[0].ids, vec![1, 4]); + } + + #[test] + fn different_environments_can_look_like_a_conflict_but_both_rules_remain() { + let memories = vec![ + dated( + 2, + "Never keep server logs for seven days during staging deployments.", + 20, + ), + dated( + 1, + "Always keep server logs for seven days during production deployments.", + 10, + ), + ]; + let (kept, held, flagged) = screened(memories, &Checkout::nowhere()); + assert_eq!(kept.len(), 2); + assert!(held.is_empty()); + assert_eq!(flagged.len(), 1); } #[test] @@ -860,24 +909,26 @@ mod tests { }; let report = Report { recalled: vec![memory("Never squash commits into main.", "")], - held: vec![ + flagged: vec![ finding(vec![1, 4], "#1 says `always` where #4 says `never`."), finding(vec![1, 6], "#1 and #6 say nearly the same thing."), - finding( - vec![3], - "#3 names `src/router.rs`, which is no longer there.", - ), ], + held: vec![Finding { + concern: Concern::Stale, + ids: vec![3], + note: "#3 names `src/router.rs`, which is no longer there.".to_owned(), + missing: vec!["src/router.rs".to_owned()], + }], ..report() }; // Drift stays out of the user's notice. assert_eq!(notice(&report), "Synapse connected · 128 memories"); let told = context(&report); - assert!(told.contains("held back 2 more"), "{told}"); + assert!(told.contains("held back 1 memory"), "{told}"); assert!(told.contains("- #3 names `src/router.rs`, which is no longer there.")); - assert!(told.contains("Prefer the memories above")); + assert!(told.contains("Both sides remain visible")); let after = restored(&report); - assert!(after.contains("held back 2 more"), "{after}"); + assert!(after.contains("held back 1 memory"), "{after}"); assert!( !context(&Report { held: Vec::new(), diff --git a/crates/synapsecore/src/mcp/server.rs b/crates/synapsecore/src/mcp/server.rs index 089edf6..d0df390 100644 --- a/crates/synapsecore/src/mcp/server.rs +++ b/crates/synapsecore/src/mcp/server.rs @@ -101,7 +101,7 @@ impl Server { } #[tool( - description = "Recall durable context with a focused query and the smallest practical limit. Use the lean budget first; a per-call budget can only reduce the configured ceiling. For an `abridged` hit, use `readmemory` with its id and the same project to read the exact text in bounded pages. `drift` marks hits that look stale or that another memory contradicts or repeats; prefer the newer one." + description = "Recall durable context with a focused query and the smallest practical limit. Use the lean budget first; a per-call budget can only reduce the configured ceiling. For an `abridged` hit, use `readmemory` with its id and the same project to read the exact text in bounded pages. `drift` marks hits that look stale or that another memory may contradict or repeat; compare contexts before treating a pair as a correction." )] async fn recall( &self, diff --git a/crates/synapsecore/src/usage.rs b/crates/synapsecore/src/usage.rs index 527e186..17e3839 100644 --- a/crates/synapsecore/src/usage.rs +++ b/crates/synapsecore/src/usage.rs @@ -15,7 +15,7 @@ mod report; pub use format::{approximately, money, thousands}; pub use price::{DEFAULTPRICE, dollars, price, setprice}; -pub use record::{Entry, Kind, bodies, hooked, record, standing}; +pub use record::{Entry, Kind, RETENTIONDAYS, bodies, hooked, record, standing}; pub use report::{Day, Report, Totals, report}; /// Characters per token. An estimate, not a tokenizer. diff --git a/crates/synapsecore/src/usage/record.rs b/crates/synapsecore/src/usage/record.rs index 3f67ddc..7a4d3af 100644 --- a/crates/synapsecore/src/usage/record.rs +++ b/crates/synapsecore/src/usage/record.rs @@ -7,6 +7,9 @@ use std::time::{SystemTime, UNIX_EPOCH}; /// that never reach a model. const HOOKED: &[&str] = &["claude-code", "pi"]; +/// Keep the ledger bounded while retaining enough history for long reports. +pub const RETENTIONDAYS: i64 = 730; + #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Kind { /// Session start, or the restart after a compaction. @@ -100,6 +103,7 @@ pub async fn record(brain: &Brain, entry: Entry) -> Result<()> { .duration_since(UNIX_EPOCH) .map(|elapsed| elapsed.as_secs() as i64) .unwrap_or_default(); + let mut transaction = brain.pool().begin().await?; sqlx::query( "INSERT INTO usage(kind, memories, reach, matched, sent, overhead, created) \ VALUES (?, ?, ?, ?, ?, ?, ?)", @@ -111,8 +115,14 @@ pub async fn record(brain: &Brain, entry: Entry) -> Result<()> { .bind(entry.sent as i64) .bind(entry.overhead as i64) .bind(created) - .execute(brain.pool()) + .execute(&mut *transaction) .await .context("could not record usage")?; + sqlx::query("DELETE FROM usage WHERE created < ?") + .bind(created - RETENTIONDAYS * 86_400) + .execute(&mut *transaction) + .await + .context("could not prune old usage")?; + transaction.commit().await?; Ok(()) } diff --git a/crates/synapsecore/tests/drift.rs b/crates/synapsecore/tests/drift.rs new file mode 100644 index 0000000..dda5bfc --- /dev/null +++ b/crates/synapsecore/tests/drift.rs @@ -0,0 +1,31 @@ +use synapsecore::brain::Brain; + +#[tokio::test] +async fn a_dense_store_has_bounded_drift_output() { + let directory = tempfile::tempdir().unwrap(); + let brain = Brain::open(directory.path().join("brain.db")) + .await + .unwrap(); + let mut transaction = brain.pool().begin().await.unwrap(); + for id in 1..=5_000 { + sqlx::query("INSERT INTO memory(rowid, body, source, created) VALUES (?, ?, '', ?)") + .bind(id) + .bind("Always keep server logs for seven days during production deployments.") + .bind(id) + .execute(&mut *transaction) + .await + .unwrap(); + } + sqlx::query( + "INSERT INTO memorymeta(memoryid, scope, project, native, created) \ + SELECT rowid, 'global', '', 1, CAST(created AS INTEGER) FROM memory", + ) + .execute(&mut *transaction) + .await + .unwrap(); + transaction.commit().await.unwrap(); + + let drift = brain.drift(None).await.unwrap(); + assert!(drift.partial); + assert!(drift.findings.len() <= 1_000); +} diff --git a/crates/synapsecore/tests/usage.rs b/crates/synapsecore/tests/usage.rs new file mode 100644 index 0000000..cf82116 --- /dev/null +++ b/crates/synapsecore/tests/usage.rs @@ -0,0 +1,30 @@ +use std::time::{SystemTime, UNIX_EPOCH}; +use synapsecore::brain::Brain; +use synapsecore::usage::{self, Entry, RETENTIONDAYS}; + +#[tokio::test] +async fn recording_usage_prunes_old_rows_and_keeps_recent_history() { + let directory = tempfile::tempdir().unwrap(); + let brain = Brain::open(directory.path().join("brain.db")) + .await + .unwrap(); + let now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() as i64; + for age in [RETENTIONDAYS + 1, 1] { + sqlx::query( + "INSERT INTO usage(kind, memories, reach, matched, sent, overhead, created) \ + VALUES ('session', 0, 100, 0, 0, 0, ?)", + ) + .bind(now - age * 86_400) + .execute(brain.pool()) + .await + .unwrap(); + } + + usage::record(&brain, Entry::session(200, 0)).await.unwrap(); + let report = usage::report(&brain, None).await.unwrap(); + assert_eq!(report.totals.sessions, 2); + assert_eq!(report.totals.reach, 300); +} diff --git a/site/docs/cli/index.html b/site/docs/cli/index.html index a78e0f4..edb1f6b 100644 --- a/site/docs/cli/index.html +++ b/site/docs/cli/index.html @@ -963,7 +963,7 @@

path

savings

synapse savings [--days &lt;n&gt;|--all] [--json] -

Tokens and dollars Synapse kept out of your sessions, compared with keeping the same memory in an instruction file every session loads. Counts what each session could reach, what recall and the session hook actually sent, and what Synapse's own tool definitions and guidance cost. Defaults to the last 30 days. Figures are characters divided by four, counted once per session, so they understate.

+

Tokens and dollars Synapse kept out of your sessions, compared with keeping the same memory in an instruction file every session loads. Counts what each session could reach, what recall and the session hook actually sent, and what Synapse's own tool definitions and guidance cost. Defaults to the last 30 days. Usage older than 730 days is pruned as new activity is recorded. Figures are characters divided by four, counted once per session, so they understate.

diff --git a/site/docs/install/index.html b/site/docs/install/index.html index dafc00e..df752f9 100644 --- a/site/docs/install/index.html +++ b/site/docs/install/index.html @@ -4,10 +4,10 @@ Install and connect — Synapse - + - + @@ -85,19 +85,20 @@

Tutorials

Install and connect

- Install the macOS desktop app or build the Linux CLI, then connect your coding tools. + Install Synapse on macOS or Windows, or build the Linux CLI, then connect your coding tools.

Requirements

  • macOS desktop: Apple-silicon Mac running macOS 13 or later.
  • +
  • Windows desktop: a Windows x64 machine with Direct3D 11.
  • Linux CLI and MCP server: a current stable Rust toolchain and a C compiler/linker to build from source.
  • At least one supported tool on PATH: Codex, Claude Code, pi, or Ainz.
  • A writable ~/.local/bin, or a custom path supplied through SYNAPSE_BIN.

Install on Linux

-

Linux supports the CLI and MCP server through synapsecore. This build does not depend on the desktop UI. Current release downloads contain the macOS app; build the Linux version from the source repository.

+

Linux supports the CLI and MCP server through synapsecore. This build does not depend on the desktop UI. Release downloads contain the macOS app and Windows installer; build the Linux version from the source repository.

From the repository root, run:

@@ -126,6 +127,9 @@

Install the macOS app

The installed CLI is a small launcher that points into the signed application bundle. Moving the app later would leave that launcher pointing at the old location.

+

Install on Windows

+

Download the synapse.<version>.windows.x64.exe installer from the latest release. It installs the app and CLI for your user account. Open a new terminal after installation to use synapse by name. See the Windows guide for build and verification steps.

+

Install the CLI

Open Settings in Synapse and choose Install CLI, or run the application binary once from a terminal and use:

@@ -221,7 +225,7 @@

Verify the connection

diff --git a/site/index.html b/site/index.html index f35f661..6256a3c 100644 --- a/site/index.html +++ b/site/index.html @@ -12,10 +12,10 @@ Synapse — shared memory for coding agents - + - + @@ -59,7 +59,7 @@

Stop repeating yourself to coding agents.

Download for macOS Try a handoff - macOS desktop · Linux terminal, CLI & MCP · Linux setup + macOS and Windows desktop · Linux CLI & MCP · Install options
@@ -165,7 +165,7 @@

Start in minutes. Go deeper when you need to.

diff --git a/site/search.json b/site/search.json index daae295..78edd9f 100644 --- a/site/search.json +++ b/site/search.json @@ -1 +1 @@ -[{"title":"Start here","description":"Understand the Synapse model, choose a path through the guide, and know where its guarantees stop.","path":"docs/","text":"The model Synapse is a local service with two deliberately separate jobs. Its memory layer gives connected developer tools one durable, searchable context store. Its vault layer gives commands or opted-in shells carefully scoped environment variables while keeping secret values sealed in a store of their own. The desktop app is the control surface. The CLI exposes the same memory, vault, scope, settings, and data-lifecycle operations. The MCP stdio server gives connected tools three narrow capabilities: remember durable context, recall it, and inspect value-free vault status. An optional agent mesh adds a fourth job on top of the same store: letting those connected tools coordinate with each other. What Synapse stores Material Location Visible to Memory text, source, global or project scope, origin, and import history Local SQLite database Desktop app, CLI, and scoped MCP memory tools Shared working guidance SOUL.md in the Synapse data directory You and every connected tool through managed global pointers Vault names, secret labels, account references, global mappings brain.db , the local SQLite database Desktop app and CLI; MCP receives names only Secret values vault.db , sealed under the key in vault.key — or macOS Keychain, if that is what the machine is set to. Never brain.db . Synapse, a child launched with synapse run , or processes launched from an activated shell Project and folder mappings Approved .synapse.yaml files You, Synapse, and the repository if you commit the file Scope approvals Local SQLite database as a path and content digest Synapse Mesh roster, channels, and messages between agents Local SQLite database, while the mesh is on Desktop app, CLI, and agents that have joined Agent Skills library The Synapse data directory, copied into each tool's own skills folder You, and every tool that reads the Agent Skills format Skills agents proposed, and what a skill used to say Local SQLite database, while self-improvement is on You. A proposed skill reaches no tool until you approve it Agent roles and team rosters TOML in the project's .synapse folder or the Synapse data directory You, Synapse, and the repository if you commit them What Synapse does not do It does not sync memory or secrets to an account or hosted service. It does not send secret values through MCP, write them to YAML, or accept them as command arguments. It cannot directly modify an already-running parent process. Use synapse run -- <command> for one child, or explicitly evaluate synapse hook so your shell applies quoted environment changes itself. It does not decide that every conversation detail deserves permanent memory. Connected tools receive instructions to keep stable, confirmed context. It does not continuously mirror native tool stores. Imports are explicit, previewed, and reversible; new shared memory belongs in Synapse. Synapse provides a signed Apple-silicon desktop app for macOS 13 or later, plus a Linux CLI and MCP server. See Linux setup to build from source. Choose a path I want to use Synapse now. Follow Install and connect your first tools . It covers download, first launch, the CLI, supported harnesses, and a working recall check. I use a different coding harness. See supported and custom harnesses . Create a .synapse/tools/mytool.toml descriptor, connect it, and contribute it through a PR. I want to manage Synapse from the app. Use the desktop app reference for every screen, status, confirmation, editor, and shell-integration control. I want to understand memory behavior. Read Memory and recall , then follow Carry one decision between tools . I need credentials for local commands. Read Vaults and scopes , then build a complete approved scope in Use a scoped secret in either shell mode . I want my tools to work together. Read Agent mesh . It covers turning it on, roles, teams, background workers, and what the mesh deliberately does not do. I want to talk to my agents rather than watch them. synapse mux puts you on the mesh under your own name. Hand a job to one agent walks through describing an outcome and letting it decide whether the work needs a team. I keep re-deriving the same procedure. Let a session write it down. Let your agents write skills covers what an agent may and may not do to your library, and why the gate is on installing rather than on writing. I keep copying the same skill into every tool. Read Skills . One library, installed into Claude Code, Codex, pi, and Ainz together, with drift reported rather than silently resolved. I configure the same MCP server in every tool, token and all. Read MCP servers . One list, given to every tool through its own mcp add , with each token in the vault instead of in plaintext in three config files. I need automation or exact syntax. Use the complete CLI reference and MCP tool reference . I am planning backups or a recovery. Read Data lifecycle before exporting or restoring the database."},{"title":"Install and connect","description":"Install the macOS desktop app or build the Linux CLI, then connect your coding tools.","path":"docs/install/","text":"Requirements macOS desktop: Apple-silicon Mac running macOS 13 or later. Linux CLI and MCP server: a current stable Rust toolchain and a C compiler/linker to build from source. At least one supported tool on PATH : Codex, Claude Code, pi, or Ainz. A writable ~/.local/bin , or a custom path supplied through SYNAPSE_BIN . Install on Linux Linux supports the CLI and MCP server through synapsecore . This build does not depend on the desktop UI. Current release downloads contain the macOS app; build the Linux version from the source repository . From the repository root, run: shell Copy cargo build --release --locked --manifest-path crates/synapsecore/Cargo.toml ./crates/synapsecore/target/release/synapse-cli install export PATH="$HOME/.local/bin:$PATH" synapse connect synapse status The install command copies the binary to ~/.local/bin/synapse . Keep that directory on your shell's PATH . synapse connect connects tools installed on this machine; restart them to load the connection. Use synapse status for text status or synapse mcp to run the MCP server. On Linux, credentials use Synapse's encrypted vault. The Keychain backend and the native desktop app are macOS-specific. Continue with Verify the connection ; the app and app-based setup steps below apply to macOS. Install the macOS app Download synapse.zip from the latest release . Extract the archive and move synapse.app into Applications . Do not install the CLI while the app is still inside Downloads or a mounted disk image. Open Synapse. The app is Developer ID signed and notarized by Apple, so it opens under default Gatekeeper settings with no security override. Why move it first? The installed CLI is a small launcher that points into the signed application bundle. Moving the app later would leave that launcher pointing at the old location. Install the CLI Open Settings in Synapse and choose Install CLI , or run the application binary once from a terminal and use: shell Copy synapse install synapse path synapse version The default destination is ~/.local/bin/synapse . If your shell cannot find it, add that folder to PATH and start a new shell. shell Copy export PATH="$HOME/.local/bin:$PATH" Synapse never overwrites an unrelated executable. A managed launcher has a sibling .synapsereceipt containing its digest so future installs can distinguish an update from a conflict. Enable shell integration In Settings → Shell environments , choose Enable shell hook . Synapse detects your default zsh, bash, or fish shell, installs the CLI if needed, and adds one marked block to its startup file. Open a new terminal after the setting changes. Shell Managed startup file zsh ~/.zshrc bash on macOS ~/.bash_profile fish ~/.config/fish/config.fish The app shows Needs repair if the managed block changes. Repair replaces only that block. Remove deletes only that block; already-running terminals retain the loaded hook until they close. Every changed existing startup file receives a .synapsebackup sibling and an atomic replacement. Connect tools See supported and custom harnesses to add a connector with a TOML descriptor or contribute one through a PR. On the macOS Synapse dashboard, each detected tool shows its installation and connection state. Choose Connect for Codex, Claude Code, pi, or Ainz. Setup performs two changes as one rollback-protected operation: It registers the installed Synapse executable as a user-level MCP stdio server with the single argument mcp . pi has no MCP client, so its step is pi install npm:synapse-pi instead: one package that starts the same server and offers the same tools. It creates SOUL.md when needed and appends or refreshes a small managed pointer in the tool’s global instruction file. Existing user content remains outside that block. The connected tool follows that pointer on launch, and the MCP server loads the same SOUL.md during initialization. The default guidance tells every connected tool to recall global plus current-project context, save confirmed reusable facts with project scope by default, use a Lean response first, and keep secrets out of memory. It also tells them to make the link visible: the first reply of a session opens with a single line such as Synapse connected · 3 memories recalled , or Synapse unavailable with a short reason when a call fails. The MCP server supplies that instruction itself, so guidance written before this behavior existed still produces the line without editing SOUL.md . Tool MCP store Instruction file Codex ~/.codex/config.toml ~/.codex/AGENTS.md Claude Code ~/.claude.json ~/.claude/CLAUDE.md pi ~/.pi/agent/settings.json ~/.pi/agent/APPEND_SYSTEM.md Ainz ~/Library/Application Support/ainz/mcp.toml ~/Library/Application Support/ainz/AGENTS.md Before changing a tool store or instruction file, Synapse creates a sibling .synapsebackup . If either half of setup fails, both files are restored. Use one shared guidance file Open Settings → Shared guidance . Open shared guidance edits the central SOUL.md . Sync pointers creates it if needed and refreshes every global pointer while preserving every line outside Synapse's managed blocks. If you want those global files to contain only those pointers, choose Consolidate guidance and confirm. Synapse moves their unmanaged content into SOUL.md , deduplicates simple shared rules, writes pointer-only global files, and keeps .synapsebackup siblings. This operation is never performed implicitly during connection setup. shell Copy synapse guidance show synapse guidance sync synapse guidance adopt --confirm Manual connection The app is the recommended setup path because it detects stale executable paths and repairs them. If you need to register the server manually, use the installed CLI path: shell Copy codex mcp add synapse -- ~/.local/bin/synapse mcp claude mcp add --scope user synapse -- ~/.local/bin/synapse mcp Then run synapse guidance sync so both global instruction files point to SOUL.md . Do not point an integration at a build artifact such as target/debug/synapse ; cleaning the repository would break the connection. Verify the connection Restart the connected tool so it reloads its MCP servers and instructions. Inspect its available tools. Synapse should expose remember , recall , readmemory , and vaultstatus . Ask it to remember one harmless confirmed convention, then recall it in a new session. Inspect the entry and its source in the macOS Memories screen, or use synapse memory list and synapse memory show <id> on Linux. If the dashboard shows a registered but disconnected state, the stored path is stale or its arguments differ from [\"mcp\"] . Choose Repair in the app or remove and re-add the manual entry."},{"title":"Supported and custom harnesses","description":"Connect a supported coding agent, describe your own with one TOML file, or contribute a connector.","path":"docs/harnesses/","text":"Supported harnesses A harness is the coding agent you run. Synapse ships connectors for Claude Code ( claude ), Codex ( codex ), pi ( pi ), and Ainz ( ainz ). Claude Code, Codex, and Ainz connect through MCP. pi uses the synapse-pi extension. Each connector declares shared guidance and skill locations, connection detection, and launch arguments. Claude Code and pi also have dedicated session integration. A custom descriptor does not acquire those hooks by declaring the same paths. shell Copy synapse tool list synapse tool show codex synapse connect codex Create a connector Install your harness and make its executable available on PATH . From your repository root, run: shell Copy synapse tool create mytool Synapse opens a commented template using VISUAL , then EDITOR , or vi if neither is set. Use an editor executable that waits until you finish editing. Replace the sample paths and arguments with your harness's actual settings. Synapse checks the descriptor before saving it to .synapse/tools/mytool.toml ; an invalid draft does not replace the saved file. Validation checks the descriptor structure, not whether your harness accepts its commands. Use synapse tool create mytool --user to save under the tools/ folder in Synapse's data directory. Run synapse path to locate that directory. A project descriptor overrides a user descriptor with the same name, and a user descriptor overrides a built-in. Use a lowercase name such as mytool ; it becomes the filename and the name passed to connection commands. You can share a project descriptor in your repository. Run the commands here from that repository's root so creation, inspection, and connection use the same project layer. Harness descriptors live in .synapse/tools/ ; .synapse.yaml configures project credential scopes. Descriptor example This example assumes a fictional harness named mytool with an mcp add command and a JSON MCP registry. Adapt it to your harness before connecting. toml Copy name = "My Tool" command = "mytool" [home] default = ".mytool" [paths] instructions = "{home}/AGENTS.md" settings = "{home}/settings.json" integration = "{home}/settings.json" skills = "{home}/skills" projectskills = ".mytool/skills" [connect] add = ["mcp", "add", "synapse", "--", "{server}", "mcp"] remove = ["mcp", "remove", "synapse"] [detect] format = "json" at = ["mcpServers", "synapse"] args = ["mcp"] [launch] prompt = ["{prompt}"] command names the executable. Connection arguments are separate array entries passed to that executable. home.default is relative to the user's home. Use {configdir}/mytool for the platform configuration directory, or set home.env if the harness supports a home-directory environment override. {home} in paths refers to that resolved harness directory. projectskills is relative to the project root. {server} expands to the Synapse executable. The harness must support the registration and removal commands you supply. detect.format accepts json or toml . detect.at names the key path to the Synapse server entry, whose command and arguments Synapse checks. Launch slots such as prompt , headless , config , and model are optional. Declare only flags your harness supports. A descriptor with an interactive prompt alone does not establish unattended mesh support. Use synapse tool show <name> to inspect a built-in example. A harness with a different transport, configuration schema, or lifecycle hook may need a code adapter; a descriptor cannot implement a protocol. Verify and update shell Copy synapse tool show mytool synapse connect mytool synapse status synapse launch mytool --print Restart the harness. Confirm it exposes Synapse's memory tools, then save and recall a harmless project convention. Inspect its instruction file for the shared guidance pointer. If it supports Agent Skills, test installing a skill with synapse skill install <skill> --tool mytool . To revise the descriptor, run synapse tool edit mytool , followed by synapse connect mytool --refresh . Add --user to the edit command for a personal descriptor. The show command reports the winning layer, which helps identify a project override. Test cleanup with synapse disconnect mytool and check that unrelated configuration remains intact. Deleting a descriptor with synapse tool delete mytool does not disconnect the harness; disconnect first if you are removing the integration. Contribute a harness PRs for additional harnesses are welcome at wess/synapse . You can use your custom connector while the PR is under review. Add the tested descriptor to crates/synapsecore/assets/tools/<name>.toml and add its embedded entry to BUILTINS in crates/synapsecore/src/agent/tool.rs . Include tests for the resolved paths, connection detection, registration and removal. Cover launch arguments and skill installation for the features you declare. The custom-harness integration test in crates/synapsecore/tests/cli.rs demonstrates the full connection lifecycle with a fake executable. State the harness version and operating systems tested. Check that setup and disconnect preserve unrelated settings and instructions. Update the README support table and this guide. Document limitations or any adapter code needed for special behavior. shell Copy cargo test --locked --manifest-path crates/synapsecore/Cargo.toml bun run site bun run sitecheck"},{"title":"Desktop app reference","description":"Use every Synapse screen with clear data boundaries, confirmation behavior, recovery paths, and shell-integration states.","path":"docs/app/","text":"Navigation and shared state The desktop app is a local control surface over the same SQLite database, vault, scope files, and tool configuration used by the CLI and MCP server. A column down the left moves between eight screens, grouped by what they answer: what Synapse is wired into, what is running, and what it is holding for you. Screen Use it for Material it can change Map See the whole store at once: which projects it holds anything about, where the weight is, and what a memory sits next to. Nothing. The map reads. Connections Detect and connect supported developer tools. Tool MCP configuration and managed instruction blocks. Memory Import, scope, search, inspect, correct, or remove durable context. Memory rows, scope metadata, and reversible import batches. MCP Keep one list of MCP servers, give it to every connected tool, and find servers in the registry. Server files in the Synapse data directory, their secrets in the vault, and each tool's MCP entries through its own CLI. Mesh Turn the agent mesh on and watch who has joined, what they report, and what they send each other. One local preference. The roster and messages are written by the agents themselves. Skills Keep one Agent Skills library, install it into every connected tool, and find skills other people published. The library in the Synapse data directory, and skill folders inside each tool. Vaults Manage labels, stored values, mappings, the value store itself, and approved project scopes. Vault metadata, stored values, and .synapse.yaml . Settings Manage shared guidance, recall, the mesh, self-improvement, the worker limit, appearance, CLI, and shell integration. SOUL.md , global pointers, local preferences, the CLI launcher, and one managed shell block. Success and error notices appear inside the active screen. The app does not require an account and does not send this state to a hosted service. Map Every other screen is a list, and a list answers what is stored rather than what it is about. The Map draws the store as a graph: one circle per memory, one larger circle per project, and lines between them. Projects come out as clusters, a correction comes out as a line to the thing it corrected, and a memory nothing else touches comes out looking like one — usually either the most interesting memory on the map or the one that should not be there. A point is sized by how much is tied to it and coloured by the project it belongs to. Click one to read that memory beside the map, with its scope, source, and date. A hollow point is a superseded memory: out of recall, still in the store, still restorable. The map is laid out in three dimensions, and the window lets you move around inside it: Do this Get this Drag Turn the cloud. Nearer points are drawn larger and brighter. Scroll Closer or further out, toward whatever the pointer is over. Shift-drag Slide the cloud within the frame. Click a point Read that memory beside the map. Reset view Put the camera back where it started. It appears once you have moved it. A plane is the wrong shape for a graph: a cluster with more neighbours than the plane has room for either overlaps itself or pushes everything else off the map, and both of those are the picture lying about the store. Turning it re-draws, never re-lays-out, so the shape you learned stays the shape it is. The plot is dark whatever theme you use. A glow is light added to what is behind it, and on a light background there is nothing to add to; the rest of the window stays in your theme. Three kinds of line, and only one of them is inferred: A memory and its project Recorded. Every memory hangs off the project it was stored for, or off global memory. A memory and the one that replaced it Recorded, and drawn in red. This is what supersession looks like from above. Two memories using the same uncommon words Inferred, and drawn faintest. It uses the same word list recall uses, so a search and a map cannot disagree about which words mean nothing. It is a resemblance, never a claim that two memories say the same thing. The map is bounded on purpose: it draws the newest 160 memories and says so whenever the store is larger. The layout is the same every time you open it, because a map is worth having when you have learned its shape. The map only reads Nothing on this screen changes anything. It opens the store when you arrive and when you press Refresh, and holds nothing between them. Connections The Connections screen reports memory count, database size, and how many supported tools are connected. It draws two lists: Connected , the tools wired in right now, above Supported , everything else Synapse can connect on this machine. A tool appears in one or the other, never both, and moves between them as you connect and disconnect it. Each tool row has one of four states: Not installed The command was not found on PATH . Set up is disabled until the tool is installed. Detected The tool executable is available but its exact synapse MCP entry is absent or stale. Choose Set up . Connected The stored executable and [\"mcp\"] arguments match the expected Synapse server. Update available Connected, and this release would connect it differently — its descriptor changed after the connection was made. Nothing is broken; there is a newer answer to apply. Choose Update . Set up registers the MCP server, creates SOUL.md when needed, and adds a marked pointer to that shared file in the tool's global instructions. For Claude Code it also installs the startup notice described below. Existing settings and text outside the managed block remain in place; changed files receive .synapsebackup siblings. If setup fails, Synapse restores the affected files. A connected Claude Code row also reports whether it announces Synapse at startup, with an Add or Remove control beside it. Adding it writes two hooks into that tool's own settings: a SessionStart hook, so the connection is stated beside its welcome message before the model has written anything, and a PreCompact hook that asks a long session to write down what it settled before its context is compacted away. The SessionStart hook also runs after a compaction, without the notice, to hand the session its memory back. A hook installed by an earlier release is brought up to date the next time the tool starts the MCP server. It also claims the status line when nothing else has. A status line you configured yourself is never replaced — the row says your status line kept instead. Removing takes back only what Synapse wrote. Either way the change applies to the next session that tool starts, and it never requires disconnecting and reconnecting. Keeping a connection current A connected row carries three controls, in increasing order of what they cost you: Update Applies this release's descriptor to a tool that is already connected. The registration is written again rather than skipped, because detection cannot tell a descriptor that moved from one that did not — it only knows an entry is present and points at Synapse. Nothing is removed, your skills stay installed, and the tool is connected throughout. This is what the Update available badge is asking for. Reset Disconnects the tool and connects it again. The bigger hammer, and the one that costs something: disconnecting removes the skills Synapse installed for that tool, and a reset puts the connection back rather than the library. Reach for Update first; use Reset when what is on disk has to go before the new answer can land. Remove Disconnects and leaves it that way: the MCP registration, the managed instruction block, the Claude Code hooks and status line, and any skill Synapse installed for it. A skill you wrote, your own words in an instruction file, and a status line somebody else configured all stay where they are. The row moves down to Supported . Synapse knows a descriptor moved because it records which one a connection was made from. A tool connected before this release has no such record, and reads as current rather than out of date — not knowing is not the same as knowing something is stale. Edit instructions and Edit config open the actual files in the built-in editor. See Tool integration files for their exact paths. Memory The import panel previews Claude and Codex separately. Import safe stores recognized project memory, skips anything credential-shaped, leaves source files untouched, and records a reversible batch. Review source opens the provider folder. Undo requires confirmation and preserves imported records that were edited or linked from another source. An empty search shows recent memory; a query searches the stored body and source and shows up to 100 results. Select an entry to inspect its ID, local creation time, full Markdown body, source, and visibility. Global makes it available everywhere; Project needs a project. The Project field shows the key the memory is filed under: a repository's remote such as github.com/owner/repo , or a folder path for a project without one. Leave it as it is to keep the memory where it is, or enter a folder path or another key to move it. Save changes replaces the selected body, source, and scope in place. Delete changes to Confirm delete before removing one entry. Wipe memories separately changes to Confirm wipe before deleting the entire memory table. A wipe does not affect vault labels, stored values, scope approvals, or settings. Recall optimization changes responses, not what this screen stores or displays. Read Memory and recall for search behavior, response budgets, and CLI equivalents. MCP The MCP screen has the same two tabs as Skills. Installed is a card per library server with the command it runs and a badge per tool. A server that still needs a secret says so with a Set button beside each one, and the value goes from that field into the vault; Install stays off until nothing is missing. Edit opens the server's file, and a draft that is not a valid server is not saved. Servers your tools already have appear under Already in your tools with Adopt , and an adopted server offers Swap to replace that tool's own entry, plaintext and all, with the library's. Search asks the official MCP Registry, which can take half a minute. Selecting a result shows the exact command it would become; Add to library keeps it without touching any tool. See MCP servers for the file format, the states, and what is and is not kept. Mesh The Mesh screen is off until you turn it on, here or in Settings. While it is off the screen explains the trade: the coordination tools are loaded by every connected tool, and that costs context in each session. Once on, it lists the agents that have joined with their role, project, and last reported work state; the background workers running under a Synapse session; and the recent messages between them. Nothing on this screen changes what agents do — it reports. Refresh re-reads the database, which is also what opening the screen does. See Agent mesh for roles, teams, and the command line. Skills The Skills screen lists your library, and beside each skill a badge per connected tool saying whether that tool has it, has an older copy, or has one Synapse did not write. Install on a skill copies it wherever it is missing or behind; Install all does the lot. Skills a tool already has that the library does not know about appear under Already in your tools with an Adopt button, which copies one into the library and starts managing it. A skill Synapse did not install is never overwritten or deleted from this screen — it is reported instead, and the result line says how many were left alone. The Search tab asks skills.sh. Results are listed down the left; selecting one reads its whole SKILL.md into the panel beside them without keeping anything, and Add to library brings it in waiting for review, so it shows up on the Installed tab with Approve and Turn down rather than in any tool. See Skills for the format, the exact folders, and what each state means. Vaults and scopes Create a vault, select it, then provide a label, environment name, and value under Add a secret . Save to the vault writes the value straight into the value store while brain.db keeps only its label, environment name, account reference, and scope state. The app never shows a saved value again. Project only requires an approved YAML mapping; Global makes that environment name available without a project mapping. Copy puts one value on the clipboard. It is the only way a stored value comes back out, and it never passes through the window. Replace reads the current Secret value field and overwrites the stored value without changing its reference. Forget requires Confirm , then removes the stored value and its metadata. Delete vault is available only when the selected vault is empty and requires a second confirmation. A badge beside the counts names the store this machine keeps values in — Encrypted vault.db or macOS Keychain — and the button next to it moves every value into the other one. That takes two clicks: the first asks, the second migrates. See Where values live for what the two stores protect. Under Project and folder scopes , choose a directory, create or edit its .synapse.yaml , inspect the reported state, then choose Approve only after reviewing the exact file. Any later edit invalidates that digest and requires another review. Secret values never enter YAML. See Vaults and scopes for resolution order and both process boundaries. Settings Shared guidance Open shared guidance edits SOUL.md . Sync pointers refreshes both global files without removing unmanaged content. Consolidate guidance requires confirmation, moves existing global text into the shared file, and leaves backups before making both global files pointer-only. Recall optimization Full, Balanced, and Lean change the shared MCP response limit and character budget. Original memory remains untouched. Balanced is the default; exact limits are documented in Response budgets . Agent mesh Off and On switch the coordination tools for every connected tool. Off keeps the tool list at its smallest, which is why it is the default. Tools already running keep the tool list they started with, so the change applies the next time each one starts. The same switch is on the Mesh screen. Self-improvement Off and On decide whether connected sessions get teach and revise . Off is the default, for the same reason the mesh is: two more tool definitions cost context in every session that loads them. A skill an agent writes waits on the Skills screen and reaches no tool until you approve it, so leaving this on never changes how a session behaves without you having read what changed it. Background workers The most agents one supervisor may run at once. Every worker is a separate session on the account you already pay for, so this is a spending limit as much as a performance one. Whatever you choose is clamped to a ceiling in code, so a mistyped number cannot buy an unbounded fleet, and synapse settings workers <count> takes any value up to it. A supervisor already running picks a change up on its next spawn. Appearance System follows the current macOS appearance as it changes. Light and Dark pin the app to that mode. The preference is stored locally. Command line The status is Installed, Not installed, or Conflict. Install CLI places the managed launcher at the displayed path. Synapse refuses to overwrite an unrelated executable; resolve a Conflict deliberately before trying again. Shell environments Command scoped always remains available through synapse run -- <command> . Automatic directory loading is an explicit opt-in for the detected default zsh, bash, or fish shell. Only that detected shell is changed. Status Control Result Not enabled Enable shell hook Installs the CLI if needed and adds one marked startup-file block. Enabled Remove hook Removes only the marked block and leaves neighboring startup content intact. Needs repair Repair hook or Remove Replaces or removes only the changed managed block. Unavailable Unavailable No supported default shell or safe startup path could be detected; no file is changed. Open a new terminal after enabling, repairing, or removing the hook. Existing terminals retain the integration they already loaded. Ambient values are readable by every child of an activated shell; use the command-scoped mode for a sensitive one-off process. Startup files stay user-owned Synapse backs up and atomically rewrites the detected startup file, follows an existing symlink, and refuses malformed or duplicate managed markers instead of guessing what to replace. Editors, local data, and recovery The built-in editor handles SOUL.md , supported tool instructions, TOML or JSON configuration, and YAML scope files. Structured formats must validate before saving. Changed files are backed up and replaced atomically while existing permissions and symlinks are preserved. If an editor contains unsaved changes, Close and application quit are blocked until you choose Save or Discard. Saving a scope refreshes its state but does not approve it; review the result and choose Approve separately. Open data folder on the Connections screen reveals the directory containing the local database. It does not create a backup and editing database files by hand is unsupported. Use Data lifecycle for integrity checks, validated exports, exclusive restore, and recovery behavior."},{"title":"Memory and recall","description":"Know what becomes durable, how search works, how to correct stored context, and what each response budget changes.","path":"docs/memory/","text":"What belongs in durable memory Good memory remains useful after the current conversation ends. Store confirmed decisions, recurring preferences, corrected assumptions, naming conventions, project constraints, and facts whose source is clear. Shared guidance lives in SOUL.md . Each connected tool's global instruction file contains a managed pointer to it, and the MCP server loads the same file during initialization. Both tools therefore receive the same recall, storage, scope, and token-budget policy. Do not store transient task status, speculative ideas, full conversation transcripts, secret values, or instructions that attempt to override the current request or repository guidance. Synapse is the canonical durable memory store; it does not create separate Markdown files for individual memories. shell Copy printf '%s ' 'Use Bun for JavaScript tasks in this repository.' \\ | synapse memory add tooling memory add and memory edit read the body from stdin. This keeps long content and shell metacharacters out of command arguments. Empty or whitespace-only memory is rejected. Credentials are taken out before anything is stored, whether the memory comes from a tool, the CLI, the desktop editor, or an import. A value assigned to a secret-sounding name ( GITHUB_TOKEN=… , --password=… ), a password inside a URL, a Bearer token, a provider token with a known prefix ( sk- , ghp_ , AKIA , xoxb- , and others), and a PEM private key block are each replaced with [redacted] , and anything between <private> and </private> is removed. The variable name stays, so the memory still says what to set. A reference such as GITHUB_TOKEN=$(gh auth token) is kept as written. remember reports how many parts it removed. Values belong in the vault . Global and project scope Project memory is the default. Synapse resolves the supplied path to its repository or approved project root and returns that memory only alongside global memory for the same project. Unrelated project memory stays out of MCP recall. A repository with an origin remote is filed under that remote, normalized to host/owner/repo with any credentials dropped, rather than under the folder it is checked out in. Every checkout of github.com/you/app shares one project memory, wherever it is: a second clone, a worktree, or the same repository after you move or rename its folder. A folder without a remote is keyed by its path. Memory an earlier release stored under a checkout's path moves to the remote's key the first time this release opens the store; a path whose folder is gone stays as it was. shell Copy synapse memory show 24 # Project: github.com/you/app Use global scope only for preferences and conventions that should follow you everywhere. The desktop editor can move a record between scopes. The CLI accepts an explicit scope when adding: shell Copy printf '%s ' 'Use Bun in this repository.' | synapse memory add tooling --project . printf '%s ' 'Prefer concise status updates.' | synapse memory add preferences --global Import existing memory The Memory screen previews Claude and Codex independently, maps each item to its project when possible, and imports safe entries into the same scoped store. Source files are read-only: Synapse never edits or deletes them. Claude imports leaf Markdown files from project memory directories and skips MEMORY.md indexes. Codex imports processed memory rows from a recognized local memory database and maps thread IDs to project roots through the local thread catalog. Full conversations, session history, tasks, settings, authentication files, and global instruction files are not imported. Credential-shaped entries are held for review. Their content is hidden in previews and is not imported by the app. Repeated imports are idempotent. Exact scoped duplicates are linked instead of copied, and each batch can be undone without removing a memory that was edited or shared by another source. shell Copy synapse memory import claude synapse memory import claude --confirm synapse memory import codex --confirm synapse memory import markdown ./notes --confirm synapse memory imports synapse memory undo <batch> --confirm Review before overriding the guard The CLI can include flagged entries only with both --include-flagged and --confirm. Review the named source file first; memory is plain text and available to connected tools. Search and recall Memory is stored in SQLite FTS5 and searched two ways at once. A word index, stemmed, finds a word in another form: deploying finds a memory that says deploys . A fragment index, by three-character pieces, finds part of an identifier or a word the stemmer does not know: hashpw finds scripts/hashpw.sh . Each ranks the query on its own and the two lists are merged by reciprocal rank, so a memory both like comes first. The body and the source are both searched, the source at under a third of the weight, so a memory filed under tooling is found by that word without outranking one that says it. An empty query returns recent entries. Connected tools are instructed to begin with a focused query, the smallest useful result limit, and a Lean response. When a result has to be shortened to fit the response budget, it is cut to its opening line if that line contains a word the query matched. If it doesn't, the passage around the match is sent instead, marked with … at both cut ends. Either way the result is marked abridged , and readmemory returns the whole text. json Copy { "query": "project naming conventions", "limit": 4, "budget": "lean", "project": "/Users/example/project" } The optional per-call budget may be full , balanced , or lean . It can only make the response smaller than the mode selected in Settings; a tool cannot override a user-selected Lean ceiling with Full. shell Copy synapse memory list "Bun JavaScript" synapse memory list --json synapse memory show 24 synapse memory show 24 --json Measuring recall Whether a change to how memory is written or searched made recall better is a question with an answer. Write down questions an agent asks, with text a useful answer contains, and score them against the store: toml Copy [[case]] query = "which javascript runtime" project = "/Users/example/project" expect = ["bun"] [[case]] query = "where do credentials live" expect = ["vault", "keychain"] # Nothing stored answers this, so the best recall returns nothing. [[case]] query = "kubernetes autoscaling" shell Copy synapse memory eval cases.toml synapse memory eval cases.toml --budget lean --fail-under 0.8 Each case runs through the same scoped recall a tool gets. The report gives the share of questions whose answer ranked first, the share whose answer came back at all, mean reciprocal rank, shown (the share whose answer text survived the response budget, which --budget sets for the run), and noise : how many memories came back for questions nothing answers, each of which an agent reads for nothing. --fail-under exits non-zero when the found share is below the floor, so a cases file can guard recall in CI. Matching is by text, case-insensitive, so one file keeps working across machines and corrections. The CLI lists up to 100 matching memories across the local store for management. Each record contains an integer ID, exact body, source, global or project scope, project key, and Unix timestamp. JSON output is suitable for local automation. Ranked search drops words that appear in nearly every memory — the , are , where — because a memory that matches only on one of those is a confident wrong answer, and an agent acts on it. Words that carry meaning in a preference, including not , never , and use , are kept. Two commands exist for when that is the wrong behaviour or the wrong result: shell Copy synapse memory grep -- --no-verify synapse memory list where are the credentials --explain memory grep matches the characters you give it and nothing else, which is what you want for an identifier, a flag, a path, or a word the ranked search treats as noise. --explain answers the other question — why a memory you know is stored did not come back: text Copy Query: where are the credentials Mode: search Expression: "credentials" Fragments: "credentials" Searched: credentials Dropped: where, are, the (matches nearly every memory) Matches: 1 0.0328 31 global vault Credentials live in the vault, never in the repository. The score is the fused rank, and higher is better. A memory ranked first by both indexes scores about 0.033; one ranked first by only one of them scores about half that. Inspect and correct The desktop Memories screen searches the same store and lets you inspect, edit, or delete individual entries. CLI edits replace the body and optionally the source: shell Copy printf '%s ' 'Use Bun unless a task explicitly requires another runtime.' \\ | synapse memory edit 24 tooling synapse memory show 24 Editing changes the stored original. Recall optimization does not. See the whole store at once Search answers a question you already have. The Map answers the one you do not: what is in here. It draws every memory as a circle, clustered by project, with a line to whatever replaced it and a fainter line between memories that use the same uncommon words. Click one to read it. It is laid out in three dimensions: drag to turn it, scroll to move closer, and click a point to read it. It reads the newest 160 memories and says so when the store is larger, without changing anything. Correcting a memory A convention changes. Something you stored last month is now wrong. Adding the new version on its own leaves two memories contradicting each other, both are recalled, and the ranking decides which one a tool acts on — possibly the one you had already retracted. Say which replaced which: shell Copy printf '%s ' 'Deploys run from the release branch, after the tag is signed.' \\ | synapse memory add deploys --global synapse memory supersede 12 47 A connected tool does it in one call, passing the id that came back from its own recall : json Copy { "content": "Deploys run from the release branch, after the tag is signed.", "scope": "global", "supersedes": [12] } Nothing is deleted. Memory 12 keeps its id, stays in memory list and the dashboards marked as replaced, still says what replaced it, and comes back at any time: shell Copy synapse memory show 12 synapse memory restore 12 What changes is only what recall can see. A superseded memory stops being returned to tools and stops counting toward the number a session reports — the count a tool announces is the count it can actually draw on. Deleting the replacement restores what it replaced on its own, so a correction can never leave the older version hidden behind an id that no longer exists. Choose between the three: edit when the memory was badly worded and there is nothing to keep, supersede when the old version was true and stopped being true, and delete when it should never have been stored. Supersession stays on this machine Sync carries a memory's content, not what replaced it. A memory superseded here arrives on another machine live. Until the wire format grows an operation for it, correct it on each machine you sync to. Response budgets Mode Result limit Character budget Transformation Full 25 Unlimited Returns stored formatting without compaction. Balanced 8 6,000 Compacts prose whitespace, preserves fenced and indented code, removes exact duplicate bodies, and replaces any memory too large for the remaining budget with its opening sentence, or with the passage its query matched. Lean 4 2,800 Uses the same non-destructive compaction with a smaller response. The response reports the optimization mode actually applied. Choose the ceiling in Settings → Recall optimization , or use the CLI: shell Copy synapse settings show synapse settings optimize lean synapse settings optimize balanced A memory that will not fit in what is left of the budget is returned as its opening sentence, marked abridged , rather than cut off wherever the character count landed. When a query found it by something its opening does not say, the passage around the match is returned instead, with … at both cut ends; see Search and recall . Half a memory reads exactly like a whole one — never deploy from main unless is a rule with its condition amputated — and one long memory at the top of the results no longer costs you every result under it. A tool that receives an abridged memory uses readmemory with its id and the same project to read the exact body in bounded pages. Originals remain intact Balanced and Lean transform only the MCP response. The database and memory-management views keep the complete stored body, abridged results included. Similar wording is not enough to discard a memory: negation, numbers, and short identifiers can change the meaning. Distinct bodies remain eligible until explicitly superseded. Search keeps non-Latin terms instead of treating them as empty queries. Destructive actions Deleting one memory, undoing an import, and wiping every memory require explicit confirmation. The desktop app presents a separate confirmation. A wipe removes memory and import history but leaves guidance, vault labels, stored values, scope approvals, and settings intact. shell Copy synapse memory delete 24 --confirm synapse memory wipe --confirm Before a large cleanup, create a portable snapshot with synapse data export . Use a wipe when the goal is to clear context while retaining the vault setup; use a database restore when the goal is to return the entire Synapse state to an earlier snapshot."},{"title":"MCP tool reference","description":"Run the local stdio server and understand the exact request, response, and value boundary of every exposed tool.","path":"docs/mcp/","text":"Server Synapse implements an MCP stdio server. A tool launches the same signed or installed executable with the mcp argument: shell Copy ~/.local/bin/synapse mcp The process uses stdin and stdout for protocol messages. Do not wrap it in a command that writes banners or shell setup output to stdout. The server opens the same local database as the desktop app and holds a shared lifecycle lock while connected. Session start Connecting Claude Code also installs a SessionStart hook that runs synapse session . It does two things a tool cannot do for itself: it prints a line in the terminal before the model has written anything, and it puts this project's memory into the session's context before the first turn. A connected pi runs the same command from its extension and shows the same two halves. shell Copy synapse session --json json Copy { "systemMessage": "Synapse connected · 128 memories", "hookSpecificOutput": { "hookEventName": "SessionStart", "additionalContext": "Synapse is connected and holds 128 memories for github.com/example/project. …" } } additionalContext carries the memories themselves, most recent first, under the same scope rule recall uses: everything global, plus everything stored for this project, and nothing from any other project. A session therefore opens already holding what the project has decided, rather than being asked to go and look. That matters because the alternative was guidance. Asking a model to call recall before it starts is an instruction it may or may not follow, and a session that skipped it worked from nothing while still reporting a connection. Guidance still asks for recall , because a focused query in the middle of a task is the case a session-start recall cannot cover. Behavior Detail Budget Recalls under a balanced ceiling. A per-call budget can only shrink your configured one, so a store set to lean still returns lean . Scope Global memory plus the project the calling tool reports through cwd or workspace.current_dir , falling back to SYNAPSE_PROJECT_DIR . Empty store No block is injected. The context asks the tool to call remember once something durable is settled. Failure Reports Synapse unavailable with a short reason and tells the model not to claim a connection that is not there. The hook never exits non-zero, because a failing hook is noise in your terminal. Trust Recalled content is labelled as context, never as instruction. It does not override the current request, repository guidance, or what you say next. After compaction The hook runs again with source set to compact . It prints no notice and hands the memory back, since the compaction dropped it. See Before compaction . Codex has no equivalent hook Codex does not expose a session-start hook, so a Codex session still opens by calling recall itself as the shared guidance in SOUL.md asks. Everything else — the tools, the scope rule, the response budget — is identical. pi reaches the server through a package pi has no MCP client, so its connection is the synapse-pi package. The extension in it starts the same server, registers whatever tools that server advertises, runs the same session-start recall, and shows the same status line. Turn the mesh on and its sixteen tools appear in pi too, on the next start. Before compaction The other end of the same session. When a long session is about to be compacted, everything it worked out that nobody wrote down is about to stop existing — and it is the only moment where not having written something down costs immediately. Connecting Claude Code installs a PreCompact hook that runs synapse compact ; a connected pi runs the same command from session_before_compact . shell Copy synapse compact json Copy { "hookSpecificOutput": { "hookEventName": "PreCompact", "additionalContext": "Synapse holds this project's durable memory, and this session is about to be compacted…" } } It asks for an explicit list of what the session settled and is not already stored, and for a remember call for each one. It deliberately recalls nothing : the context window is being reclaimed, and spending it re-injecting memory the session already had is the opposite of what a compaction is for. Afterwards, Claude Code starts the session hook again with source set to compact . That run prints no connection notice, since you saw it when the session began, and hands the memory back: the same recent memory the session started with, re-read, so anything it remembered before compacting comes back too. A hook installed by an earlier release is updated to fire on compaction the next time the MCP server starts. The compaction itself is never blocked, cancelled, or rewritten. A memory tool that traded a session's whole context for a reminder would be a worse bargain than forgetting. remember Stores a durable fact, decision, preference, convention, or correction. json Copy { "content": "Use small focused modules.", "source": "synapse", "scope": "project", "project": "/Users/example/project", "supersedes": [18] } Field Type Required Meaning content string Yes The durable text. Empty or whitespace-only content is rejected. Credentials and <private> sections are removed before it is stored; see What belongs in durable memory . source string or null No An origin such as a project path, repository name, or topic. scope project or global No Defaults to project. Use global only for context that should appear everywhere. project string or null For project scope Absolute working-project path. Synapse resolves nested paths to the project root, then files the memory under that repository's origin remote ( host/owner/repo ) when it has one, and under the root's path when it does not. supersedes array of integers or null No Ids of memories this one replaces — usually ids that came back from an earlier recall . Each stops being recalled. Nothing is deleted. An id that no longer resolves is skipped rather than failing the write. json Copy { "id": 24, "stored": true, "superseded": [18] } redacted appears only when something was taken out before storing, and says how many parts: a credential or a <private> section. The stored text is then not the text that was sent, so do not rely on the removed value being in memory. The store and the replacement happen in one transaction. Without supersedes , a correction is a second memory contradicting the first, both come back from the next recall, and the ranking decides which one an agent acts on — including the version its own author had already retracted. recall Returns durable context relevant to a query. A word matches in any form, so deploying finds deploys ; part of an identifier matches too, so hashpw finds scripts/hashpw.sh ; and a memory's source is searched at a lower weight than its body. An empty query returns recent memory. Search and recall has the details. json Copy { "query": "module structure", "limit": 8, "budget": "lean", "project": "/Users/example/project" } Field Type Required Meaning query string Yes Words or a phrase describing the context needed. Use an empty string for recent entries. limit integer or null No Requested result count. Defaults to 8. The active response budget may lower it. budget full , balanced , or lean No May reduce the configured response ceiling but can never enlarge it. project string or null No Absolute working-project path. Results include global memory plus this project, in any checkout of the same repository, and exclude other projects. json Copy { "optimization": "lean", "memories": [ { "id": 24, "body": "Use small focused modules.", "source": "synapse", "scope": "project", "project": "github.com/example/project", "created": 1785250000 } ] } Two fields appear only when they are true of the result. abridged marks a memory returned in part, because the response budget could not carry the rest. The part is its opening sentence when that sentence holds a word the query matched, and otherwise the passage around the match, cut with … at both ends — use readmemory with its id and the same project before acting on the part you cannot see. superseded never appears here at all, because a replaced memory is not recalled. readmemory Read one memory by id after recall identifies it. The body is an exact page of stored text, including code and whitespace. No model or additional index is involved. json Copy { "id": 24, "project": "/Users/example/project", "offset": 0, "budget": "lean" } Field Type Required Meaning id positive integer Yes Memory id from recall. project string or null No The same absolute project path used for recall. Scope resolution is identical. offset nonnegative integer or null No UTF-8 byte offset, initially zero. Use the preceding page's next value to continue. budget full , balanced , or lean No Can reduce the configured ceiling. Lean pages contain at most 2,800 bytes; Balanced and Full pages at most 6,000 bytes. json Copy { "optimization": "lean", "memory": { "id": 24, "body": "Use small focused modules.", "source": "synapse", "sourceabridged": false, "scope": "project", "project": "github.com/example/project", "created": 1785250000, "offset": 0, "next": null, "total": 26 } } next is null at the end. Otherwise, pass it as offset with the same id and project. total is the stored body's UTF-8 byte length. Byte limits stay within the character ceiling and never split a character. An offset inside a character or beyond the body is an error. The source label has a separate 240-byte allowance; sourceabridged says whether it was shortened. Scope, project, creation time, and id retain the memory's provenance. Metadata and JSON framing are additional to the body budget. A missing, superseded, or out-of-scope id returns {\"optimization\":\"lean\",\"memory\":null} under Lean. Each page reads the current stored memory. If the memory is edited while reading, restart at zero; pages do not hold a historical snapshot. Hidden history remains available to its owner through synapse memory show and synapse memory restore . vaultstatus Lists active environment-variable names and scope trust state for a folder. It never returns secret values and cannot inject them into the connected tool. json Copy { "path": "/Users/example/project" } path is optional. Resolution falls back to SYNAPSE_PROJECT_DIR , then the server process’s current directory. json Copy { "path": "/Users/example/project", "backend": "encrypted", "available": ["DATABASE_URL"], "unavailable": ["deploy.RegistryToken"], "elsewhere": 4, "ambient": "ready", "shell": "zsh", "scopes": [ { "path": "/Users/example/project/.synapse.yaml", "scope": "project", "trusted": true, "changed": false, "env": ["DATABASE_URL"], "denied": [], "error": null } ], "warnings": [], "note": "Values stay in the vault and never in a response. A name under available resolves for this folder; one under unavailable is named by a scope file here and needs it approved first. elsewhere counts secrets held for other folders, which are never named." } Metadata, not a secret channel The names in available tell a tool what a scoped command or activated shell could receive. vaultstatus never reads the corresponding values and cannot change a connected tool’s environment. backend names the store this machine keeps them in, which is a fact about the machine and not a way to reach one. Nothing here reports a credential belonging to a folder other than the one asked about. unavailable names the secrets this folder asks for and cannot reach, as vault.name . Only ones a .synapse.yaml on the way down to the folder names, so every name in it is already written in a file the caller can open; a long list is truncated and the remainder joins the count. Everything else this machine holds is counted by elsewhere and never named — this tool reaches every connected tool, and an agent working on one project has no business being handed the names of another project's credentials. The pair exists because an empty available is otherwise the same answer whether nothing is stored or nothing is approved here, and those have different fixes. When a folder resolves nothing and the store is not empty, warnings says so and names the step that would change it — synapse scope init , then naming the secret in the env: block, then synapse scope trust . teach and revise Two tools that appear only while synapse settings learn on is set. They are how a session writes down a procedure it worked out, and corrects one that turned out wrong. Both write to the Synapse skill library and to nothing else. teach takes a name , a one-line description , the instructions as Markdown, a scope of project (the default) or global , the absolute project root, and a note for you saying why it was worth keeping. Synapse writes the frontmatter itself rather than accepting it, so a model never gets to invent YAML keys or a name that disagrees with its own directory. The result always says the same thing: stored, and waiting for you. revise takes the name , the corrected instructions in full, and a note saying what was wrong with the version it replaces. It returns the revision id holding the old text and the tools the correction reached. Teaching is free; installing is the decision A taught skill is in the library and in no tool until you approve it, so a session can never change how the next one behaves. A revision is the exception and reaches the copies Synapse installed — you already agreed to that skill being loaded, and a correction that never arrives leaves every session running the version that was wrong. What it said before is kept either way. See the skills guide . Mesh tools Three tools are always present. Sixteen more appear only while the agent mesh is switched on, and two more while self-improvement is, because a tool definition costs context in every session that loads it. They let connected sessions register under a name, message each other directly or by channel, park on wait until work arrives, report and watch work state, and start or stop background workers. The guidance explaining them is sent with them and withdrawn with them, so the tool list and the instructions can never be out of step. That is true of both switches. See the agent mesh guide for the full list and what each one is for. Operational behavior Tool errors are returned as readable strings. Protocol messages remain on stdio. The server identifies itself as synapse using the application version and advertises tool capability only. Opening the database runs integrity and relationship checks, applies numbered migrations, and secures database permissions. A running MCP process holds a shared database lock. synapse data restore requires an exclusive lock and therefore refuses while any connected server or the desktop app is using the database. Connected tools should recall before decisions that depend on project history and remember only stable confirmed context after it is established. A Claude Code session starts with that memory already in context; see Session start ."},{"title":"Agent mesh","description":"Let connected coding tools message each other, split up a job, and park for free between tasks, using the same local database as your memory.","path":"docs/mesh/","text":"What the mesh is Every tool you connect to Synapse already runs its own copy of the same local server. The mesh gives those sessions a way to reach each other: a supervisor breaks a job into tasks, hands them out by name or by channel, and collects the results, while every idle agent waits at almost no cost. There is no daemon, no port, and no token. The message bus is the same local database that holds your memory, so a message between two agents never leaves your Mac and is inspectable with the same tools as everything else Synapse stores. The mesh is off until you turn it on. Its tools are loaded into every connected session, and that costs context in each one, so Synapse does not add them to a setup that will not use them. undefined Turn it on Use Settings → Agent mesh in the app, or the command line: shell Copy synapse settings mesh on A tool that is already running keeps the tool list it started with. Restart it, or open a new session, to pick the change up. The same is true when you turn the mesh back off. How agents join Switching the mesh on makes the tools available; it does not put anyone on the mesh. A session joins when it calls register with a name of its own, which happens when you ask it to work with other agents or when Synapse launched it with a role. Tool What it does register Join under a unique name. Called once, before anything else. send , post , broadcast Message one agent, a channel, or everyone. join , leave Subscribe to and unsubscribe from a channel. wait Block until work arrives. This is how an agent stays reachable while doing nothing. inbox Take whatever is waiting right now, without blocking. reportstatus , waitstatus Report working, blocked, or done with a one-line note saying what you are doing, and block until a teammate reaches one of those. agents , channels , whoami See who is here, what channels exist, and your own place in it. spawn , workers , stopworker Grow, inspect, and shut down a team of background workers. A parked wait returns an empty list after a few idle minutes and the agent simply calls it again, so an idle teammate costs one tool call every few minutes rather than a loop that spins. Roles A role is the durable identity an agent launches with: a brief describing what it owns and how it coordinates, plus optional defaults. It is separate from a task, which is the one-off assignment. Synapse ships with supervisor , overseer , worker , frontend , backend , reviewer , devops , and qa . shell Copy synapse relay role list synapse relay role show frontend synapse relay role create reviewer # writes into this project synapse relay role create reviewer --user # writes into your own layer Roles resolve from the project first, then your own layer, then the built-ins. A role saved into a project lives in .synapse/roles/ and travels with the checkout. Editing a built-in copies it down into a layer you own, so the shipped templates stay intact. toml Copy channels = ["frontend"] tool = "claude" # model = "claude-opus-5" # driver = true # stay interactive instead of parking # tools = ["Read", "Edit", "Bash(git:*)"] # pre-granted tool rules description = """ You own the frontend. Follow the existing component conventions and report blockers to the supervisor. """ Teams A team is a named roster. Opening one launches every member at once: the first is the lead and runs in your terminal so you have someone to steer, and the rest run in the background. shell Copy synapse relay team list synapse relay team open web Closing the lead stops its team. The command that opened the team is the process supervising it, so nothing is left running behind you. toml Copy [[member]] name = "lead" role = "supervisor" [[member]] name = "backend" role = "backend" Background workers A supervisor can also grow its own team as it works, by calling spawn . A worker started that way runs headless, registers itself, and parks on wait until it is given something to do. Because nobody is watching a headless session, it runs with its own tool's permission prompts bypassed. Workers belong to the session that started them, for as long as that session lives. A worker that exits is restarted with a backoff that grows, and one that never manages a healthy run is retired rather than restarted forever. At most eight run at once. shell Copy synapse relay launch backend --role backend --task "build the login API" synapse relay ps synapse relay kill backend A launched agent is given the project folder it should work in, and reaches Synapse through the same connection it already has. Nothing generates a credential, and no secret value is ever passed to a launched agent. undefined Driving a team yourself Everything above puts a lead agent between you and the work: you brief the lead, the lead hands out tasks and relays the answers back. That costs the lead's context on every message and makes it a bottleneck for a job it is not doing. synapse mux puts you on the mesh instead. You get a name on the roster and the same messaging every agent already has, so you can address any agent directly. shell Copy synapse mux --team pair @backend the created column needs a default, not a backfill #build freezing the schema in ten minutes ! stop and report where you are /focus backend and the index too A bare line goes to whoever is focused, so a back-and-forth with one agent reads like a conversation. /help lists the rest: /agents , /workers , /log <name> for what a worker has been doing, /kill , and /quit . Leaving takes the workers it started with it. Or hand it to one agent Picking a roster before you understand the job is its own small chore, and most jobs do not need four agents. The overseer team is one agent and nothing else: shell Copy synapse mux --team overseer @overseer get the release notes written and the changelog updated You are still on the roster as yourself, so nothing is interposed — you can address any worker it starts without going through it. What changes is that you describe an outcome rather than handing out the pieces, and it grows a team with spawn only when the work turns out to want one. Its brief is explicit that starting a worker is not free and that eight is the ceiling. It launches with Claude Code unless you say otherwise. synapse relay team create overseer --user copies the file into your own layer, where a tool line names any connected tool instead. The more useful direction is the other one. A headless worker runs with its permission prompts bypassed, so when it reaches a decision it should not make alone, it has had nobody to ask — and has had to guess. With a person on the roster it can send you the question, report itself blocked, and wait for the answer. Agents can tell a person from an agent. A roster entry for a human is marked as one, and connected tools are told to ask them questions and never delegate work to them. undefined Messages reach an agent at its next check, not as an interrupt. One parked between tasks answers in about a second; one in the middle of a long build sees you when it comes back. The roster shows which is which. undefined Watching the mesh The Mesh page in the app shows who has joined, what each one last reported, which workers are running, and the recent traffic between them. The same is available from the terminal: shell Copy synapse relay status synapse relay agents synapse relay channels synapse relay feed --follow Every command takes --json for scripting. What an agent is doing A state on its own tells you an agent has not stalled. It does not tell you what it is working on, and for a headless worker there is no terminal to look at. So reportstatus takes a note as well as a state, and that note is what the roster shows: shell Copy synapse relay agents lead supervisor online working splitting the migration into three tasks /work/api backend backend online blocked need the staging database name /work/api frontend frontend online working rewriting the auth middleware /work/api The note is one line, bounded, and kept when a later report does not carry one — an agent that says working twice has not stopped doing the thing it described the first time. Reporting an empty note clears it, which is how an agent going idle drops a note about work it has finished. waitstatus returns the note with the state, so a supervisor blocking until a worker reaches blocked is told what it is blocked on in the same reply. Without that it would have to go and read the worker's log to find out, which is the thing the note exists to avoid. Notes are guidance, not a guarantee Writing one is something connected tools are told to do, not something Synapse can enforce inside another tool's session. An agent that reports a state without a note leaves the previous one standing, so a stale note is possible; the roster's last-seen time is what tells you whether the agent itself is still live. Limits A message from another agent is information, not instruction. Connected tools are told to treat mesh traffic as untrusted input that never overrides you, your shared guidance, or repository rules. Broadcast and channel history is not replayed. A new agent sees only what is sent after it joins, so start your workers before you brief them. A direct message is held for an agent that has not registered yet. Delivery is at least once. A reply lost in flight is delivered again rather than dropped; a duplicate is possible on that rare path. An agent that stops answering leaves the roster within about a minute and a half, and a session that closes takes its name with it. Agents work on your real machine with your real files. A background worker runs with its permission prompts bypassed, because there is no terminal in which to answer one."},{"title":"Skills","description":"Keep your Agent Skills in one library, install them into every connected tool, and let a session write down a procedure it worked out — for you to approve before it reaches anything.","path":"docs/skills/","text":"Why a library Claude Code, Codex, pi, and Ainz all read the Agent Skills open format, and each reads it from its own folder. A skill you want in all of them gets copied by hand as many times, and the copies start drifting the first time you improve one of them. Synapse keeps one copy. You edit it in one place, install it into every connected tool, and the app tells you which copies have fallen behind. Where skills go Place Path The Synapse library <data>/skills/<name>/SKILL.md … for one project <data>/skills/@<project>/<name>/SKILL.md Claude Code ~/.claude/skills/<name>/SKILL.md Codex ~/.agents/skills/<name>/SKILL.md pi ~/.pi/agent/skills/<name>/SKILL.md Ainz ~/Library/Application Support/ainz/skills/<name>/SKILL.md A project's own <project>/.claude/skills/ , .agents/skills/ , .pi/agent/skills/ , .ainz/skills/ Codex reads personal skills from the shared <code>~/.agents/skills</code> folder rather than from its own home. <code>~/.codex/skills</code> holds the set Codex ships with, and Synapse does not write there. undefined The format A skill is a directory with a SKILL.md at its root. The frontmatter needs a name that matches the directory and a description saying what the skill does and when to reach for it. Everything after the frontmatter is the instructions. markdown Copy --- name: release-checklist description: Walk the release checklist for this project. Use when cutting a release, tagging a version, or when the user asks what still needs doing before shipping. --- ## Steps 1. Run the full test suite. 2. Bump the version. 3. Write the release notes. A skill can carry more than its SKILL.md — scripts/ , references/ , and assets/ travel with it, and Synapse copies the whole directory. Agents load only the name and description until a task calls for the skill, so a long reference file costs nothing until it is needed. A description containing a colon followed by a space has to be quoted, or YAML reads it as a nested key. Synapse says so by name rather than passing the raw parser error through. undefined Synapse ships one skill, synapse-mesh , covering how to actually run a team of agents. It lands in your library on first use and is an ordinary editable skill from then on. Installing shell Copy synapse skill list # what is in the library synapse skill create my-workflow # start one from a template synapse skill edit my-workflow # open it in $EDITOR synapse skill install # copy everything into every tool synapse skill install my-workflow # or just one synapse skill install --tool claude # or into just one tool synapse skill status # where each one is synapse skill remove my-workflow # take it back out Editing a skill in the library leaves the installed copies behind, and status says so. Running install again brings them back in step. In the app, the Skills screen does the same thing with a button per skill and one that installs everything. What each state means State What it means What Synapse will do not installed The tool does not have this skill. Install it. installed The copy matches the library. Nothing needed. update available Synapse installed it, and the library has changed since. Install again to sync. changed in place Synapse installed it, and it has been edited inside the tool. Refuse, unless you pass --replace . not ours A skill of the same name that Synapse never wrote. Refuse. It is yours, not Synapse's. not in the library The tool has a skill Synapse does not know about. Nothing, until you adopt it. waiting for review An agent wrote it and you have not looked at it yet. Nothing, until you approve it. Synapse knows which copies are its own because it records the digest it wrote and the library digest it came from. Without that record, \"the library moved on\" and \"somebody wrote this by hand\" look identical, and the safe response to both would be to do nothing. Adopting what you already have If a tool already has a skill you want managed, adopt it. That copies it into the library and records the tool it came from as already having it, so the original stops reading as somebody else's and starts staying in step. shell Copy synapse skill adopt humanize --tool claude synapse skill install humanize The app lists these under Already in your tools on the Skills screen, with an Adopt button beside each. Finding published skills Other people publish skills too. synapse skill search asks skills.sh , a public index of the skills on GitHub that weighs a match by how many people installed it, and synapse skill get brings one into your library. shell Copy synapse skill search release notes synapse skill get phuryn/pm-skills/release-notes synapse skill show release-notes synapse skill approve release-notes A skill you get arrives proposed , exactly like one an agent wrote: it is in the library and in no tool until you approve it. It is somebody else's instructions, its description would be loaded into every session of every tool holding it, and a skill can carry scripts, so read it first. Every file is checked before anything is written — paths stay inside the skill, the frontmatter is valid, and a skill already in your library is never replaced. skills.sh is one source among any you add: a team's own GitHub repository, or another index. Every source is asked at once, and results are written source:id . See Sources . The app's Skills screen has the same thing as a second tab: Search lists the results down the left and shows the selected skill's whole SKILL.md beside them, with Add to library . Searching is the one part of the skill library that reaches the network, and only when you search. The query and the skill you fetch go to skills.sh; nothing else about your machine or library does. undefined Project skills A procedure you work out in one repository is usually about that repository. A library where every skill is global costs every session on the machine to hold one project's checklist, so skills have shelves : the library root for what is true everywhere, and one shelf per project beside it. shell Copy synapse skill create release --project # this repository's own synapse skill install release --project # into this repository's .claude/skills synapse skill list --global # or just the shared ones A project skill installs into that project's own skills folders rather than into your home, so it is loaded when you work there and nowhere else. It also travels with the repository once it is installed, which is the point. Every command that takes a bare name looks at this project's shelf before the global one, so synapse skill show release run inside a repository shows that repository's version. A global skill and a project skill can share a name without colliding; --global and --project [folder] say which you mean when it matters. Not every tool has a place for these A project skill needs a project-local skills folder, and a tool described without one simply has nowhere to put it. Synapse leaves that pairing out of skill status rather than reporting it as something to fix. Skills agents write Switch this on and a session can write down a procedure it worked out, and correct one that turned out wrong: shell Copy synapse settings learn on That adds two tools, teach and revise , to every connected session, and the guidance explaining them arrives with them. It is off until you ask for it, because a tool definition costs context in every session that loads it. Synapse has no model and runs no loop, so it never reflects on a session. The session decides what it learned; Synapse decides where that lands and who has to agree to it. Nothing reaches a tool until you say so A skill an agent writes is proposed . It is in the library and in no tool, and it stays there until you approve it. The gate is on installing rather than on writing, because the library is Synapse's own folder while a skill's description is loaded into every session of every tool holding it — so writing one costs you a line in a list, and installing it is the decision. shell Copy synapse skill proposed # what is waiting, and who wrote it synapse skill show cut-a-release # read it synapse skill approve cut-a-release # install it where it belongs synapse skill reject cut-a-release --confirm synapse skill install with no name steps over anything waiting, so a proposal never reaches a tool because you installed everything. The app shows the same queue on the Skills screen with Approve and Turn down beside each. You will also see it said once at the start of a session — Synapse connected · 1 skill to review — and nowhere else. Waiting is what a proposal is for; a queue that interrupts is not a queue. Corrections do reach the copies revise is the deliberate exception. You already agreed to that skill being loaded, and a correction that never arrives leaves every session running the version that was wrong — so a revision goes out to the copies Synapse installed. It reaches only copies Synapse wrote that nobody has edited since; one you changed by hand is yours and is left where it is. Nothing is lost doing it. What the skill said before is kept, and putting it back is one command: shell Copy synapse skill history cut-a-release # what it used to say, and what was wrong synapse skill revert cut-a-release # put the last version back Reverting is itself a revision, so a revert can be reverted. The newest twenty versions of each skill are kept. Limits Installing copies the directory rather than linking it, so a copy stays put if the library moves. The cost is that an edit in the library has to be installed again to reach the tools, which is what status is for. Deleting a skill from the library does not remove the copies already installed. Use synapse skill remove for that, before or after. A skill whose SKILL.md does not parse is reported and skipped rather than copied anywhere. A skill an agent writes is only ever a skill. It cannot install itself, cannot reach a tool, and cannot write anywhere except the library."},{"title":"MCP servers","description":"Keep one list of MCP servers, give it to every connected tool, and keep their tokens in the vault rather than in each tool's config.","path":"docs/servers/","text":"Why a library Every coding tool keeps its own list of MCP servers in its own format, so the same server ends up configured three times, and its token sits in plaintext in three files. Synapse keeps one list instead. Each server is a small TOML file in the Synapse data directory, and its secrets are held as references into the vault rather than as values. What a tool is given is not the server itself. It is synapse mcp run <name> , written through that tool's own mcp add . When the tool starts it, Synapse reads the library, fetches the secrets from the vault, and replaces itself with the real server. Nothing is proxied: the tool talks to the server directly and keeps everything its own client does, and Synapse is gone from the path the moment the server starts. Editing a server needs no reinstall. Tools only ever hold the <code>run</code> line, so the next session that starts the server picks up the change. undefined Adding a server Describe it once. Everything after the bare -- is the command, so its own flags are never read as Synapse's: shell Copy synapse mcp add github -- docker run -i --rm -e GITHUB_PERSONAL_ACCESS_TOKEN ghcr.io/github/github-mcp-server synapse mcp show github synapse mcp edit github The file it writes has a command , its args , plain settings under [env] , and secrets under [secrets] . A value can only be written under [env] , which is what keeps a token out of it: the [secrets] table holds nothing but references. toml Copy command = "docker" args = ["run", "-i", "--rm", "-e", "GITHUB_PERSONAL_ACCESS_TOKEN", "ghcr.io/github/github-mcp-server"] [secrets] GITHUB_PERSONAL_ACCESS_TOKEN = "mcp-github.github-personal-access-token" Secrets A secret is entered at a prompt or piped in, never passed as an argument, the same as every other secret Synapse holds: shell Copy synapse mcp secret github GITHUB_PERSONAL_ACCESS_TOKEN Each server gets a vault of its own, named mcp-<server> , because a vault holds one secret per environment variable and two servers wanting GITHUB_TOKEN is ordinary. In the app, a server that still needs a secret shows a Set button beside it, and the value goes from the field straight into the vault. A server is not started while anything is missing — a secret with no value, a setting left empty, or an argument still written as a <placeholder> . It is refused with a line saying what to fill in, rather than starting half-configured and failing somewhere less obvious. Installing shell Copy synapse mcp install # every server, every tool that can hold one synapse mcp install github --tool codex synapse mcp status synapse mcp uninstall github Claude Code, Codex, and Ainz can each be given library servers. pi has no MCP client of its own, so it gets no row. A tool somebody describes can take them too by adding a [servers] section to its descriptor with the add , remove , and at templates. What each state means State Meaning installed The tool starts this server through Synapse, from this binary. update available Through Synapse, but from a binary that has moved. install rewrites it. not ours The tool already has its own server by this name. It is left alone unless you say to replace it. not installed The tool does not have it. Whether an entry is Synapse's is read from the entry itself — synapse mcp run <its own name> — so there is no record to drift from what the file says. synapse disconnect and synapse uninstall take every such entry back out; a reset does not, because fixing a connection is no reason to lose a tool's servers. Adopting what you already have A server a tool already has can move into the library. Every value its entry carries goes into the vault — all of them, not only the ones whose names look secret, because a value misjudged as harmless would sit in plaintext in the library. shell Copy synapse mcp adopt github --tool claude synapse mcp install github --tool claude --replace Adopting does not touch the tool. Its own entry still holds the token until you install over it with --replace , which swaps it for the run line and takes the plaintext out. In the app, that is the Swap button, offered only for the tool the server was adopted from. Finding servers synapse mcp search asks the official MCP Registry , and synapse mcp get turns an entry into a library server: an npm package becomes npx , a PyPI one uvx , a container image docker run . Whatever the entry needs and gives no value for is left for you to fill in. shell Copy synapse mcp search postgres synapse mcp get io.github.owner/server-name synapse mcp install server-name The official registry is one source among any you add: an organisation's own registry, or a GitHub repository holding server.json files. See Sources . The registry is slow — fifteen to forty-five seconds a search when measured — and sorts by name, with nothing like a download count to rank by. Synapse puts what it can run first and waits a full minute before giving up. undefined Limits Remote servers, which a tool reaches over HTTP rather than starting, are not kept here. Add those in each tool directly. A server's secrets are handed to it whenever a tool starts it, from any folder. The mapping you set up is the permission; there is no per-project scope for library servers. Deleting a server with synapse mcp delete <name> --confirm takes it out of every tool Synapse gave it to and forgets its vault. A tool's own entry by the same name is left alone."},{"title":"Sources","description":"Choose where skill and MCP searches look: public indexes, MCP registries, and your own or your team's GitHub repositories.","path":"docs/sources/","text":"What a source is synapse skill search and synapse mcp search , and the Search tabs in the app, ask every source at once and label each result with the one it came from. A source is one of three kinds: Kind Holds What it is skills index Skills skills.sh, or anything answering its /api/search and /api/download . MCP registry MCP servers The official MCP Registry, or any registry implementing its /v0/servers API — including one an organisation runs for itself. GitHub repository Both Every SKILL.md folder and every server.json in a repository, or in one folder of it. skills.sh and the official MCP Registry are built in. Once you change the list it is yours, so a built-in can be removed like any other. Adding one shell Copy synapse source add team acme/agent-skills synapse source add team acme/agents@main/skills # a branch and a folder synapse source add internal https://mcp.example.com # a registry synapse source list synapse source remove skills.sh Synapse works out the kind. owner/repo or a github.com address is a repository; an address is asked whether it answers as an MCP registry or as a skills index, so a typo is caught when the source is added rather than on every search. --kind settles it when asking cannot. Results are written source:id , which is what get takes. A bare id still means the first index, so synapse skill get owner/repo/skill works as it always has. shell Copy synapse skill search release notes synapse skill get team:skills/release-notes synapse mcp get internal:com.example/billing In the app, the bottom of each Search tab lists the sources that tab asks, with a field to add one and Remove beside each. Private repositories A private repository reads its token from the vault, and the sources file holds only the reference: shell Copy synapse secret set work github GITHUB_TOKEN # prompted synapse source add team acme/agent-skills --token work.github A project's sources A repository can carry .synapse/sources.toml so everyone working in it searches the team's own sources: toml Copy [[source]] name = "team" kind = "github" repo = "acme/agent-skills" path = "skills" It is used only once somebody has approved its exact contents, the same bargain .synapse.yaml makes. A file somebody else committed decides where your searches go and whose instructions come back, so it is listed and not searched until you say otherwise: shell Copy synapse source allow Editing the file takes the approval away until it is allowed again. A project source whose name is already in your own list is skipped and reported, so team:release always means one thing. Approving a source is not approving what it holds. A skill from any source still arrives waiting for review, and a server still reaches no tool until you install it. undefined How a repository is read One request to GitHub's trees API lists the whole repository, and the files come from raw.githubusercontent.com, which does not count against GitHub's allowance of sixty requests an hour without a token. The catalogue — every skill's name and description, every server.json — is cached in the data folder for an hour, and a refresh whose tree has not changed reads nothing else. synapse source refresh reads it again now. A server.json straight out of a repository is often the template a release fills in, with ${VERSION} where the version goes. Synapse cannot fill it, so the server runs unpinned."},{"title":"Vaults and scopes","description":"Keep values in an encrypted store or macOS Keychain, map names through approved YAML, understand precedence, and choose a command-scoped or ambient environment.","path":"docs/vault/","text":"Storage model A vault is an organizational name. A secret record connects a vault-local label to an environment-variable name and an account reference. brain.db stores that metadata and never a value; the value itself goes to whichever store this machine keeps values in. A reference uses vault.name form, such as work.database . Scope YAML maps an environment name such as DATABASE_URL to that reference. A repository can therefore contain the mapping without containing the value. Object Example Rules Vault work Letters, numbers, and hyphens; unique name. Secret name database Letters, numbers, and hyphens; unique inside its vault. Environment name DATABASE_URL Starts with a letter or underscore; then letters, numbers, or underscores; stored uppercase. Reference work.database Vault and secret name joined by a dot. Where values live Synapse has two value stores, and the choice is yours rather than the platform's. Store What it is What it protects encrypted vault.db in the data folder, one XChaCha20-Poly1305 envelope per secret, sealed with a 32-byte key in vault.key . Both files are owner-only. A vault that has been copied — a backup, a synced folder, a disk image, a machine somebody else now has. keychain macOS Keychain, one generic password per secret under the app.synapse.vault service. The same, plus per-application access control enforced by macOS. The encrypted store is the default on a new installation and the only one available off macOS. A machine that was already holding secrets before this release stays on Keychain until you move it, so an upgrade never stops resolving a credential. shell Copy synapse vault backend synapse vault migrate keychain synapse vault migrate encrypted --keep vault backend with no argument prints the current store. With an argument it only records a choice, and refuses once secrets exist — moving is vault migrate , which copies every value, reads each one back, switches the setting, and then removes the originals. --keep leaves the originals where they are. A store that cannot be read stops the migration before anything switches, so a Keychain prompt you decline cannot strand your values. Keychain is the stronger bargain on a Mac It gates access per application, where a key file protects a vault that has been copied and not a process already running as you. It is a setting away in both directions, and neither store ever holds a value in plaintext. Create a secret shell Copy synapse vault create work synapse secret set work database DATABASE_URL synapse secret list work When stdin is a terminal, secret set reads the value with a hidden prompt and asks for confirmation. When stdin is piped, it reads the stream. The value is never accepted as a command argument. Surrounding whitespace is removed before storing, and secret set says so when it removed any: a pasted trailing space is invisible in a terminal and turns into an authentication failure indistinguishable from a wrong password. A value equal to the vault, the secret name, or the environment name is refused outright and nothing is stored — three name-shaped arguments stand in front of the one that is not a name, and this is what happens when the value lands one slot late. What was stored is then echoed back as a shape, never a value: text Copy Saved work.database in the encrypted vault · 19 chars · ••••-••••-••••-•••• The length and the punctuation are enough to recognise the credential you meant to enter, or to see that it went in one character longer than what you copied. The desktop app applies the same guards and prints the same shape. shell Copy printf '%s' "$DATABASE_URL" | synapse secret set work database DATABASE_URL Shell history boundary The safe prompt keeps the value out of history and process arguments. A piped command is only as safe as the command that produces its stdin; do not paste a value directly into a visible shell command. Add --global to make the environment name available in every folder, or change an existing label later: shell Copy synapse secret set work registry NPM_TOKEN --global synapse secret global work.registry off Get a value back A value never appears on screen, in a log, or in an MCP response. The one way to retrieve one is onto the clipboard: shell Copy synapse secret copy work.database The command prints the reference it copied and never the value. In the desktop app the same thing is a Copy button on the secret's row. On macOS the value goes through pbcopy over stdin, never as a command argument; set SYNAPSE_CLIPBOARD to name a different command. Scope files From a project folder, create .synapse.yaml : shell Copy synapse scope init . The generated project template is valid but maps nothing: yaml Copy version: 1 scope: project env: {} deny: [] Edit it to map names to existing references. Unknown fields are rejected. yaml Copy version: 1 scope: project env: DATABASE_URL: work.database NPM_TOKEN: work.registry deny: - PRODUCTION_TOKEN Use synapse scope init path/to/folder --folder for a nested folder scope. The scope field describes intent and appears in status output; resolution uses the file’s position in the ancestor path. Approval A scope has no effect until its exact contents are approved. Synapse hashes the file and stores its canonical path plus digest in SQLite: shell Copy synapse status . synapse allow synapse scope status . synapse allow approves the closest discovered scope. If several ancestor scopes exist, approve each from its own folder. synapse scope trust [folder] remains available when you want to approve an exact path directly. Changing even whitespace changes the digest. The scope then reports changed and stops applying until you inspect and approve it again. Invalid YAML and unknown references produce warnings. Both shell modes refuse an incomplete environment when any discovered scope is pending, changed, invalid, or references an unknown secret. Command-scoped mode refuses to launch; ambient mode unloads its managed values. Precedence and deny Global mappings load first. Synapse discovers every .synapse.yaml from the filesystem root toward the working folder. Approved files apply in that order, so the closest mapping replaces a broader mapping for the same environment name. A name in deny is removed and remains denied for all narrower scopes. A child scope cannot add it back. This lets a repository override a harmless global credential while permanently blocking a production credential inside a sensitive subtree. Choose an environment boundary Command scoped shell Copy cd /path/to/project synapse status . synapse run -- cargo test synapse run -- bun run deploy synapse run resolves the current working folder, verifies every discovered scope, reads each selected value out of the vault, and sets it only on the new child process. Normal environment inheritance still applies; Synapse adds or replaces the resolved names. Your current shell is unchanged. Ambient directory Open Settings → Shell environments and choose Enable shell hook . Synapse detects the default shell, installs the CLI if needed, and manages a marked startup-file block. Open a new terminal, then allow the project: shell Copy cd /path/to/project synapse allow For a temporary or manual installation, evaluate the matching hook directly: shell Copy # Add the matching line to your shell startup file. eval "$(synapse hook zsh)" # eval "$(synapse hook bash)" # synapse hook fish | source The hook loads the approved environment when the shell enters the project, unloads it when the shell leaves, and reloads it after an approved change. If Synapse replaced a variable that already existed, it restores the original value instead of unsetting it. A hook never activates from global mappings alone: the directory must contain at least one approved discovered scope. Run synapse deny to revoke the closest scope. The next prompt unloads managed values. synapse status . reports whether ambient activation is ready, blocked, or inactive and whether the current shell has a hook installed. Ambient mode has a wider boundary Every process launched from the activated shell can read the values. Prefer synapse run for a sensitive one-off command. Do not enable shell tracing such as set -x while the hook is evaluating environment changes, because tracing can print plaintext values. MCP vaultstatus and CLI status report names, scope states, and warnings. They do not read or reveal values. To remove a value and its metadata, run synapse secret forget work.database . A vault can be deleted only after all of its secrets are forgotten."},{"title":"Data lifecycle","description":"Locate the database, understand startup checks and automatic backups, export a consistent snapshot, and restore safely.","path":"docs/data/","text":"Files and permissions On macOS, the default data directory is ~/Library/Application Support/synapse . Run synapse path to print the resolved data, shared-guidance, and CLI paths for the current environment. Path Purpose brain.db Scoped memory, import provenance and batches, settings, vault metadata, global mappings, and scope approvals. SOUL.md The editable shared guidance loaded by both connected tools and the MCP server. brain.db-wal and brain.db-shm SQLite write-ahead-log sidecars while the database is active. brain.lock Shared lifecycle lock held by the app, CLI operations, and MCP server. backups/ Automatic pre-migration and pre-restore SQLite snapshots. On Unix systems the data and backup directories use mode 0700 ; database files, sidecars, locks, and backups use 0600 . Startup Every database open follows the same sequence: Create and secure the data directory if needed. Acquire a shared lifecycle lock. Open SQLite with foreign keys enabled, a five-second busy timeout, and WAL journal mode. Run PRAGMA foreign_key_check , and PRAGMA quick_check when this store has not already been read. Reject a database newer than the current application supports. Create a pre-migration snapshot when an existing database needs a numbered migration, then apply the migration transactionally. Move project memory filed under a repository's folder path to that repository's origin key, when the folder is still a repository with a remote. A store with nothing to move costs one indexed read. Reapply owner-only file permissions. <code>quick_check</code> reads every page, so its cost grows with everything you have stored. Synapse runs it once for a given file rather than once per handle, and reporting commands such as the status line skip it: they redraw constantly, and a whole-store scan there would cost far more than the number they print. <code>synapse data check</code> always runs the full check. undefined shell Copy synapse data check synapse data check --json Automatic backups Before changing an existing schema, Synapse creates backups/brain.<timestamp>.v<version>.db . Before replacing an existing database during restore, it creates backups/brain.<timestamp>.restore.db . Upgrading to schema 13 rebuilds the search index: memory is stemmed, sources become searchable, and a fragment index is added beside it. Like every migration it is one-way. An earlier Synapse refuses a store this release has opened, and says so. To go back, close Synapse and restore the .v12.db snapshot it took first. These are complete snapshots created through SQLite VACUUM INTO , not copies of a live database file. Synapse does not currently rotate them; include the folder in your normal local backup policy and remove old snapshots deliberately. Export a portable snapshot shell Copy synapse data export "$HOME/Desktop/synapsebackup.db" synapse data check --json The destination must not already exist. Synapse opens the active database normally, writes a consistent compact snapshot, secures its permissions, then reopens it read-only and validates integrity before reporting success. Secret values are separate The export covers brain.db: memory, vault metadata, and account references, and never a value. Values live in their own store — vault.db beside it, or macOS Keychain — and neither is in the snapshot. Restoring on another machine restores the labels; the values are set again there, or vault.db and vault.key are carried across together and treated like the secret they are. Restore a snapshot Quit the Synapse desktop app. Stop every connected MCP process by closing the relevant tool sessions. Run the restore command from a separate terminal. shell Copy synapse data restore "$HOME/Desktop/synapsebackup.db" synapse data check Restore opens the source read-only, validates page and relationship integrity, and requires its schema version to match the current release. It then tries to acquire the exclusive lifecycle lock. If the app or any MCP server still holds the database, restore refuses without changing anything. When a current database exists, Synapse validates it and creates a recovery snapshot before atomically replacing it. WAL and SHM sidecars are cleared, file permissions are secured, and the containing directory is synced. Memory wipe is not a reset synapse memory wipe --confirm removes every memory entry and import batch but leaves SOUL.md , settings, vault labels, stored values, global mappings, and scope approvals. Use it when you want a clean memory history without rebuilding guidance or credential setup. There is no one-command factory reset. To remove Synapse completely, first forget each secret through the app or CLI, then quit all processes and remove the application, installed launcher, and data directory deliberately. On the encrypted store the data directory is the credential store, so removing it takes every value with it — synapse uninstall --data says so before it acts."},{"title":"Configuration and paths","description":"Find every Synapse-owned path, understand environment overrides, and know how integration and editor writes are protected.","path":"docs/config/","text":"Resolved paths shell Copy synapse path Item Default on macOS Home ~ Data directory ~/Library/Application Support/synapse Database ~/Library/Application Support/synapse/brain.db Shared guidance ~/Library/Application Support/synapse/SOUL.md Skill library ~/Library/Application Support/synapse/skills/ Mesh roles and teams ~/Library/Application Support/synapse/roles/ , teams/ Worker logs ~/Library/Application Support/synapse/workers/ Database backups ~/Library/Application Support/synapse/backups/ Installed CLI ~/.local/bin/synapse Everything above lives under one folder. A project may also hold .synapse.yaml scope files and a .synapse/roles/ directory, both of which travel with the checkout rather than with you. Environment variables Name Effect Typical use SYNAPSE_DATA Replaces the application-data directory. Development, tests, or a deliberately isolated database. SYNAPSE_HOME Replaces the home directory used for tool files and the default CLI path. Integration testing without touching real user files. SYNAPSE_BIN Replaces the full CLI installation destination. Install outside ~/.local/bin . SYNAPSE_PROJECT_DIR Provides the fallback project folder for MCP calls that do not carry one, and is what a launched agent is told to treat as home. A client that launches the server outside the project working directory. Set automatically by synapse launch and relay launch . SYNAPSE_PAGE Chooses which dashboard page opens first: map , memory , mesh , skills , vaults , or settings . Anything else opens Connections. Opening the app straight to the screen you want. CODEX_HOME Replaces the Codex home directory used for detection and for reading an existing Codex memory store. A non-default Codex installation, or testing an import without touching real files. PI_CODING_AGENT_DIR pi's own variable for its agent directory, which Synapse reads for the same reason: detection, the instruction pointer, and the skills folder. A non-default pi installation. SYNAPSE_PI_PACKAGE Replaces the package a connection to pi installs, and the source detection compares against. Defaults to npm:synapse-pi . Working on the package from a checkout instead of npm. SYNAPSE_COMMAND The Synapse executable a launched pi session should talk to. Set automatically by synapse launch pi so a session reaches the binary that started it. SYNAPSE_SHELL_ACTIVE Identifies the shell hook currently evaluated in this shell. Set automatically by the zsh, bash, or fish integration so status can report it. SYNAPSE_SHELL_KEYS Records which managed variables the hook currently has loaded, so leaving a scope unloads exactly those and restores what was there before. Set automatically by the shell hook. Not intended to be set by hand. SYNAPSE_SHELL_COMMAND The executable the installed shell block re-invokes on each directory change. Set automatically by the startup-file block so a moved app is still reachable. SYNAPSE_DOCUMENT Launches the desktop app directly into a Markdown, TOML, JSON, or YAML document. Internal editor entrypoint. shell Copy SYNAPSE_DATA="$HOME/tmp/synapsedemo" synapse data check SYNAPSE_BIN="$HOME/bin/synapse" synapse install Overrides affect only the process that receives them. Keep the desktop app, CLI, and connected MCP server on the same SYNAPSE_DATA value if they should share one store. Tool integration files Custom harness definitions live in .synapse/tools/<name>.toml for a project, or tools/ in the Synapse data directory for a user. Create one with synapse tool create <name> , adding --user for the user layer. Project definitions override user definitions, which override built-ins. See the custom harness guide for the schema and a working example. .synapse.yaml remains the credential-scope configuration. Tool Integration Instructions Settings shortcut Codex ~/.codex/config.toml ~/.codex/AGENTS.md ~/.codex/config.toml Claude Code ~/.claude.json ~/.claude/CLAUDE.md ~/.claude/settings.json pi ~/.pi/agent/settings.json ~/.pi/agent/APPEND_SYSTEM.md ~/.pi/agent/settings.json Ainz ~/Library/Application Support/ainz/mcp.toml ~/Library/Application Support/ainz/AGENTS.md ~/Library/Application Support/ainz/config.toml Connection detection parses the actual TOML or JSON entry named synapse . It reports connected only when the stored command resolves to the expected executable and the arguments equal [\"mcp\"] . A deleted development binary therefore appears stale instead of healthy. pi is the exception, because its connection is a package rather than a server command: detection reads pi's packages list and reports connected when the Synapse package is there. The extension in that package starts the same synapse mcp server and finds the executable itself, so there is no stored path to go stale. Each instruction file contains a managed pointer to the central SOUL.md . A normal sync preserves other text in place. The optional consolidation action first moves that text into SOUL.md , then leaves the global files pointer-only so shared guidance has one editable source. Connecting Claude Code also writes two entries into ~/.claude/settings.json : a SessionStart hook running synapse session , and a statusLine running synapse statusline . JSON has no comment syntax, so Synapse cannot mark its own entries the way it marks a block in a Markdown file. It recognizes them by the command they run instead, and carries everything else through untouched — a status line you configured yourself is reported and left alone, never replaced. Entry Command What it produces hooks.SessionStart synapse session The connection line in your terminal, and this project's memory in the session's context before the first turn. See Session start . statusLine synapse statusline One line under the prompt for the rest of the session: model, folder, memory count, and mesh size. Both are removed by synapse disconnect claude . Codex and Ainz expose neither, so those connections are the MCP entry and the instruction pointer only. pi has no settings entries of this kind either — its notice and status line come from the extension in its package rather than from a hook it has to be configured with. Safe writes The built-in document views validate JSON, TOML, and YAML before saving. A write with changed content: Resolves a dotfile symlink and writes through it rather than replacing the link. Writes the previous bytes to a sibling file ending in .synapsebackup . Writes the replacement to a temporary file, syncs it, preserves existing permissions, then renames it atomically. Syncs the containing directory. Tool setup snapshots its integration file, instruction file, and shared guidance file. If registration or the instruction update fails, it restores every prior state. The CLI installer applies the same rollback model to its launcher and receipt. Settings and shell integration shell Copy synapse settings show synapse settings optimize full synapse settings optimize balanced synapse settings optimize lean # Manual alternative to Settings → Enable shell hook. eval "$(synapse hook zsh)" synapse allow synapse deny The optimization value lives in SQLite, so the app, CLI, and every connected MCP process read the same setting. Balanced is the default. See Memory and recall for exact limits and transformations. The Settings screen detects the default shell and can enable, repair, or remove its managed startup-file block. Existing files are backed up and replaced atomically. The hook is an explicit per-shell opt-in: it activates only inside a directory with at least one discovered, approved scope. Leaving the scope, revoking approval, or changing the YAML unloads managed values and restores values that existed before activation. See Choose an environment boundary before enabling ambient mode."},{"title":"Security model","description":"Understand the trust boundary around local memory, stored secret values, MCP metadata, approved scopes, child processes, and configuration writes.","path":"docs/security/","text":"Trust boundary Synapse is local-first, not a sandbox. It removes unnecessary network and file exposure from the memory-and-credential workflow, but it still runs as your macOS user. Any process already able to control your account, inspect your terminal, read your home directory, or access an unlocked Keychain may sit inside the same trust boundary. The application requires no Synapse account or hosted service. MCP uses local stdio. Memory and metadata live in a local SQLite file. Synapse includes no telemetry, no remote synchronization, and no web server. Memory Memory bodies are plain text in SQLite and are available to connected MCP clients through recall. Global records are available in every project; project records are returned only with the matching project, which is the repository's origin remote when it has one and its root folder otherwise. A remote written with credentials, such as https://user:token@host/… , is stored without them. Do not store credentials, private keys, access tokens, or material you would not want an authorized connected tool to read. As a backstop, every memory written on this machine is redacted before it is stored, whether it comes from a tool, the CLI, the desktop editor, or an import: values assigned to secret-sounding names, passwords in URLs, bearer tokens, tokens with a known provider prefix, and PEM private keys become [redacted] , and <private> sections are removed. Redaction recognizes shapes; it cannot know that an arbitrary string is a secret, so it is a safety net, not permission to paste credentials. Import reads recognized durable-memory stores only. It does not inspect conversation logs, authentication files, settings, tasks, or global instructions. Credential-shaped entries are flagged, hidden in previews, and skipped by the app. Provider formats are schema-checked before use, original files remain untouched, and import batches can be undone. Database files use owner-only permissions on Unix systems and are checked for page and foreign-key integrity before use. These controls protect against accidental broad access and corruption; they do not encrypt memory at rest. Secret values A secret value is never in brain.db , never in YAML, never in a log, never in an MCP response, and never at rest in plaintext. brain.db stores the vault, label, environment name, and account reference. The value goes to one of two stores. The encrypted store is the default, and the only one available off macOS. Each value is sealed with XChaCha20-Poly1305 under a random 24-byte nonce, with its account reference authenticated as associated data, and written to vault.db in the data folder. The 32-byte key lives beside it in vault.key , created once with owner-only permissions and never overwritten. Deleted rows are zeroed rather than left on a free page. Be clear about what that protects. It protects a vault that has been copied — a backup, a synced folder, a disk image, a laptop somebody else now has. It does not protect you from a process already running as your user, which can read vault.key exactly the way Synapse reads it. This is encryption at rest, not an access control. macOS Keychain is the other store, and on a Mac it is the stronger of the two: it adds per-application access control that the operating system enforces, which a key file cannot. It is one setting away in either direction, and synapse vault migrate moves every value across without a gap. secret set does not accept a value argument. Interactive input uses a hidden prompt; piped input remains available for automation. secret copy is the only way a stored value comes back out, and it goes to the clipboard rather than to a screen, handed to pbcopy over stdin rather than as an argument that would be readable in ps . Anything on the clipboard is readable by every program that can ask for it, and Synapse does not clear it for you. Metadata can still be sensitive Names such as PRODUCTION_DATABASE_URL can reveal system structure even when the value is hidden. Choose scope files and variable names with the same care you apply to ordinary repository configuration. Scope approval An unapproved, edited, or invalid .synapse.yaml never contributes variables. Approval stores a SHA-256 digest of the exact file bytes and its canonical path. Any later edit invalidates that digest. Resolution walks from broad ancestor folders toward the working folder. Narrower approved files can replace a mapping, but a name denied by a broader scope remains denied. Any scope warning prevents synapse run from launching and makes the shell hook unload its managed values, avoiding silent partial configuration. Environment boundaries synapse run -- <command> reads only the selected values out of the vault and places them in the environment of a new child process. That command and all of its descendants can read those values normally. Synapse cannot stop the child from logging, transmitting, or persisting them. The optional zsh, bash, or fish hook asks the shell to evaluate quoted environment changes. It activates only inside a directory with at least one approved discovered scope. Leaving, revoking approval, or changing any discovered scope unloads managed variables and restores values that existed before activation. Settings installs the hook inside a marked startup-file block using the absolute managed CLI path. It backs up and atomically replaces an existing file, refuses incomplete or duplicate Synapse blocks, and removes only the marked block when disabled. Ambient access is broader Every process launched from an activated shell inherits the values, including commands that do not need them. Use command-scoped mode for a smaller boundary, and do not enable shell tracing while environment changes are evaluated. Inspect scripts before launching them with production credentials. Use folder scopes and deny to narrow access. Prefer separate least-privilege credentials for development and deployment. Use synapse status . immediately before a sensitive command. Use synapse deny when a directory should no longer activate automatically. Operational limits Synapse does not isolate a malicious connected MCP client from recalled memory. Only connect tools you trust. Both stores protect a value at rest, but an authorized local child receives plaintext environment variables. On the encrypted store, the key sits beside the sealed values in the data folder. Anything running as you can open the vault; a copy of the folder taken without the key cannot. Database exports contain brain.db only — memory and credential metadata, never a value. vault.db is a separate file for exactly that reason, and it is not in the export. Deleting the data folder deletes vault.db and vault.key with it, which is every stored value. On the Keychain store the values survive it. synapse uninstall says so before it does anything. SOUL.md is ordinary local Markdown. Both connected tools can read it, so do not put secrets there. Configuration writes are validated, backed up, and atomic, but external programs can still change the same files afterward. A scope digest proves that you approved exact bytes at a path; it does not prove the commands launched from that folder are safe. There is no remote revocation system. Forget a secret in Synapse and rotate it at its issuer when exposure is possible."},{"title":"CLI reference","description":"The complete Synapse command surface, including inputs, output modes, confirmation guards, and process behavior.","path":"docs/cli/","text":"Invocation Run synapse help for the built-in summary. Every command answers --help and -h with its own entries — synapse vault --help , or synapse secret set --help for one line of it — and synapse help <command> is the same answer typed the other way round. After a -- the flag belongs to the child, so synapse run -- mytool --help asks mytool . Commands print human-readable output unless a documented --json option is present. Errors go to stderr and return a non-zero exit status. Secret and memory input Secret values are never accepted as arguments. Secret set uses a hidden prompt or stdin. Memory add and edit always read their body from stdin. Application and server app synapse app Open the native desktop application. Running synapse mcp synapse mcp Run the MCP stdio server until the client closes the connection. The process holds a shared database lifecycle lock. run synapse run -- <command> [arguments] Resolve global, project, and folder vault mappings for the current directory, refuse on any scope warning, read the selected values out of the vault, and launch the child. Synapse returns the child exit code, or 1 when the operating system reports no code. launch synapse launch &lt;claude|codex|pi&gt; [options] [-- &lt;flags&gt;] Start a coding tool with Synapse already in place: memory and the vault reachable over MCP, this folder's vault variables in its environment, and the project root it should treat as home. Everything after a bare -- is passed to the tool untouched. A tool Synapse is not connected to is wired for the life of the process, so this works before setup has ever run and writes nothing into the tool's own configuration. Options: --directory , --model , --allow-tool (repeatable), --strict , --no-vault , --skip-permissions , --as <name> with --role , --task , and --channel to join the mesh as well, and --print to show the resolved command and environment names without running anything. Refuses on any scope warning, and never prints a secret value. status synapse status [folder] [--json] Show the resolved folder, available environment names, discovered scope states, warnings, ambient readiness, and detected shell hook. Reports which value store this machine uses and never a value. The folder defaults to the current directory. Shell environments hook synapse hook <zsh|bash|fish> Print the integration script for a shell. Evaluate or source it from that shell’s startup file. The hook reevaluates approved directory scopes after directory changes and before prompts. allow synapse allow [folder] Inspect and approve the exact contents of the closest .synapse.yaml at or above the folder. The folder defaults to the current directory. deny synapse deny [folder] Revoke approval for the closest discovered scope. An installed hook unloads its managed values at the next prompt or directory change. export synapse export <zsh|bash|fish> Internal hook protocol. Emits a shell-quoted environment diff that the installed hook evaluates. Users normally invoke hook , allow , and deny instead. Two boundaries synapse run gives one child process the resolved environment. The shell hook exposes the environment to every process launched from an activated shell until the scope unloads. Vault and scope vault list synapse vault list List vault names alphabetically. vault create synapse vault create <name> Create a unique vault. Names may contain ASCII letters, numbers, and hyphens. vault delete synapse vault delete <name> Delete an empty vault. Forget every contained secret first. vault backend synapse vault backend [&lt;keychain|encrypted&gt;] Print which store holds this machine's secret values, or choose one. Choosing moves nothing, so it refuses once secrets exist and points at vault migrate . The encrypted store is the default on a new installation and the only one off macOS. vault migrate synapse vault migrate &lt;keychain|encrypted&gt; [--keep] Move every value into the other store: copy them all, read each one back, switch the setting, then remove the originals. --keep leaves the originals in place. A source that cannot be read stops the migration with nothing moved and nothing switched. secret list synapse secret list <vault> List each vault.name reference, environment name, and whether it is global or scoped. Never prints a value. secret set synapse secret set <vault> <name> <env> [--global] Read a value from a hidden terminal prompt or stdin and save it in the vault. Creates metadata when the label is new; replaces the stored value when the environment name matches. --global also enables the mapping. secret copy synapse secret copy <vault.name> Put one value on the clipboard. The only way a stored value comes back out, and it never reaches the terminal: the command prints the reference and nothing else. Set SYNAPSE_CLIPBOARD to use something other than pbcopy . secret forget synapse secret forget <vault.name> Delete the stored value, its metadata, and any global mapping. The value is removed from both stores, so a machine that has switched backends leaves nothing behind in the one it stopped reading. secret global synapse secret global <vault.name> <on|off> Enable or disable a secret’s global environment mapping. Enabling replaces the current global source for that environment name. scope init synapse scope init [folder] [--folder] Create a new .synapse.yaml . The folder defaults to the current directory. Use --folder for a folder-kind template; the command refuses when the file already exists. scope trust synapse scope trust [folder] Approve an exact scope path directly by parsing it, calculating its content digest, and storing that digest with its canonical path. synapse allow is the directory-oriented shortcut. scope status synapse scope status [folder] [--json] Alias the scope-oriented status flow to the same resolved output as synapse status . Memory memory list synapse memory list [query] [--json] [--explain] Search the body and source with the joined query words, by word in any form and by fragment, or list recent memory when empty. Returns up to 100 entries. Text mode prints ID, scope, source, and a compact preview, and marks any memory that has been superseded. With --explain , print what the search actually did first: the full-text expression the query became, the words it searched for, the words it dropped for matching nearly everything, and the fused score behind each hit, higher being better. memory grep synapse memory grep &lt;text&gt; [--json] Find memories containing an exact string, most recent first. Nothing is tokenized, ranked, or dropped — the characters given have to appear, in that order — which is what makes it the right tool for an identifier, a flag, a path, or a word the ranked search treats as noise. Matching is case-insensitive for ASCII, and a pattern after a bare -- is taken literally, so memory grep -- --no-verify searches for the flag. memory eval synapse memory eval &lt;cases.toml&gt; [--limit &lt;n&gt;] [--budget &lt;full|balanced|lean&gt;] [--fail-under &lt;0-1&gt;] [--json] Score recall against a file of labeled questions. Each [[case]] has a query , an optional project folder, and expect : text a useful memory contains, matched case-insensitively, or nothing when no stored memory answers it. Reports how often the answer ranked first, how often it came back at all, mean reciprocal rank, how often the answer text survived the response budget, how many memories came back beside the answer, and how many came back where none answer. --limit defaults to 5; --budget sets the response budget for the run and defaults to the configured one. --fail-under exits 1 when the found share is below the floor. memory drift synapse memory drift [folder] [--json] [--strict] Check everything a folder's sessions can see for memory that no longer holds: a file path that is gone from the checkout, two memories that say opposite things or carry different numbers, and two that say the same thing. Each finding comes with the command that settles it. Nothing is changed. --strict exits 1 when anything is found. Recall and the session hook run the same check on what they hand over. memory show synapse memory show <id> [--json] Print one exact memory with scope, project, source, and timestamp, or return structured JSON. The project is a repository key such as github.com/owner/repo , or a folder path for a project with no remote. memory add synapse memory add [source] [--global|--project <folder>] Read a non-empty body from stdin. Project scope is the default and resolves from the current folder; use global only for context that belongs everywhere. Credentials and <private> sections are removed before storing, and the command says how many were. memory edit synapse memory edit <id> [source] Read the replacement body from stdin and replace one existing memory. The optional source replaces the source label. The memory keeps its project, and the new body is redacted the same way memory add redacts. memory supersede synapse memory supersede &lt;old&gt; &lt;new&gt; Stop recalling one memory in favour of another. The old one is not deleted: it keeps its id, stays listed and readable, says what replaced it, and comes back with memory restore . Use this instead of leaving a correction to argue with what it corrects. memory restore synapse memory restore &lt;id&gt; Put a superseded memory back in circulation. Deleting the memory that replaced it does the same thing on its own. memory import synapse memory import <claude|codex|markdown> [path] [--confirm] Preview recognized durable memory without changing its source. Add --confirm to import safe entries. Credential-shaped entries remain flagged unless the CLI also receives --include-flagged after source review. memory imports synapse memory imports [--json] List import batches, their provider, stored and linked counts, and whether each batch is active or undone. memory undo synapse memory undo <batch> --confirm Remove memories created only by that import batch. Preserve manually edited memories and records linked to another origin. memory delete synapse memory delete <id> --confirm Delete one memory. The exact --confirm guard is required. memory wipe synapse memory wipe --confirm Delete every memory entry and import batch while leaving SOUL.md, settings, vaults, stored values, and scope approvals intact. The exact guard is required. Agent mesh These commands need the mesh switched on with synapse settings mesh on . See the agent mesh guide for what each part is for. relay status synapse relay status [--json] Show whether the mesh is on, how many agents are reachable, and how many background workers are running. relay agents synapse relay agents [--json] List every agent with its role, reachability, last reported work state, and the project it is working in. relay channels synapse relay channels [--json] List the channels in use and how many agents subscribe to each. relay feed synapse relay feed [--follow] [--since &lt;id&gt;] [--json] Print the messages agents have sent each other. With --follow the command keeps printing new ones until interrupted. relay launch synapse relay launch &lt;name&gt; [options] Open one agent in this terminal, wired into the mesh with a role. Options: --role , --tool claude|codex|pi , --task , --channel (repeatable), --allow-tool (repeatable), --model , --directory , --lead , --optimize , --strict , --skip-permissions , --command <template> , and --print to show the resolved command without running it. relay team open synapse relay team open &lt;name&gt; [--directory &lt;folder&gt;] Open a whole roster. The first member runs in this terminal as the lead; the rest run in the background and stop when the lead closes. mux synapse mux [--as &lt;name&gt;] [--team &lt;team&gt;] [--channel &lt;name&gt;]... [--directory &lt;folder&gt;] Join the mesh as yourself and drive a team from one terminal. You get a name on the roster and the same messaging every agent has, so you can address any agent directly instead of relaying through a lead — and an agent that gets stuck can ask you . With --team the whole roster starts in the background with you as the lead. Type @name text for one agent, #channel text for a channel, !text for everyone, or a bare line to whoever is focused. /help lists the commands; /quit leaves and stops the workers it started. The name defaults to your login name. relay role synapse relay role &lt;list|show|create|edit|delete&gt; [name] [--user] [--json] Manage reusable agent roles. Create, edit, and delete write into the project by default, or into your own layer with --user . Editing a built-in copies it down first, and a file that does not parse is never saved. relay team synapse relay team &lt;list|show|create|edit|delete&gt; [name] [--user] [--json] Manage team rosters, resolved and edited exactly like roles. relay ps synapse relay ps [--json] List background workers with their state, process id, and log path. relay kill synapse relay kill &lt;name&gt; Stop a background worker. A worker owned by a Synapse session that is still running has to be stopped from there. session synapse session [--json] Report this session's Synapse connection as Claude Code session-hook output. Synapse installs this for you when you connect Claude Code; run it by hand to see exactly what a session will be told. When the input's source is compact , it prints no notice and hands the session's memory back instead, because the compaction dropped it. Reads the calling tool's JSON on stdin. compact synapse compact Answer a tool's pre-compaction hook. Asks the session to write down anything durable it settled before its context is compressed away, and recalls nothing — the window is being reclaimed. Synapse installs this for you when you connect Claude Code. Never blocks or rewrites the compaction. Reads the calling tool's JSON on stdin. statusline synapse statusline Print one status line for a connected tool, reading that tool's JSON on stdin. Skills One Agent Skills library, installed into every connected tool. See the skills guide for the format and the folders involved. skill list synapse skill list [--json] List the library with each skill's file count and description. A skill whose SKILL.md does not parse is reported on stderr and skipped. skill show synapse skill show &lt;name&gt; Print one skill's SKILL.md . skill create synapse skill create &lt;name&gt; Start a skill from a template. Names follow the standard: lowercase letters, digits, and single inner hyphens. skill edit synapse skill edit &lt;name&gt; Open a skill in $VISUAL or $EDITOR . A draft that does not parse is never saved over the working one. skill delete synapse skill delete &lt;name&gt; --confirm Remove a skill from the library. Copies already installed in tools are left alone. skill install synapse skill install [name] [--tool &lt;tool&gt;] [--replace] Copy the library into your tools. Without a name it installs everything; without --tool it installs into every connected tool. A copy that was edited in place, or a skill Synapse never wrote, is refused unless --replace is given. skill remove synapse skill remove &lt;name&gt; [--tool &lt;tool&gt;] [--force] Take a skill back out of a tool. Only a copy Synapse installed and nobody has changed is removed; --force overrides that. skill status synapse skill status [name] [--json] Show where each skill stands in each tool, and any skill a tool has that the library does not. skill adopt synapse skill adopt &lt;name&gt; [--tool &lt;tool&gt;] Copy a skill a tool already has into the library and record that tool as having it, so it stops reading as unmanaged. Every command that takes a bare name resolves this project's shelf before the global one, so a repository's own release shadows the shared one rather than colliding with it. Pass --global or --project [folder] to say which you mean. skill proposed synapse skill proposed [--json] List the skills agents have written that nobody has looked at yet, oldest first, with the tool that wrote each one and the line it left saying why. A proposed skill is in the library and in no tool. skill approve synapse skill approve &lt;name&gt; [--tool &lt;tool&gt;] Install a proposed skill where it belongs and stop calling it proposed. A global skill goes into every connected tool's personal skills folder; a project skill goes into that project's. skill reject synapse skill reject &lt;name&gt; --confirm Turn a proposed skill down. It leaves the library with its history, and nothing else on the machine ever knew about it. Only a skill still waiting for review can be rejected — once one is approved it is an ordinary skill, and skill delete is what removes it. skill history synapse skill history &lt;name&gt; [--json] Show what a skill used to say, newest first, with the line saying what was wrong with each version. The newest twenty are kept. mcp add synapse mcp add &lt;name&gt; [--description &lt;text&gt;] -- &lt;command&gt; [args...] Describe an MCP server for the library. Everything after the bare -- is its command. Nothing reaches a tool until mcp install . mcp list synapse mcp list [--json] List the library's servers, the command each runs, and anything one still needs before it can start. mcp show synapse mcp show &lt;name&gt; Print one server's description. Secrets appear as vault references, never values. mcp edit synapse mcp edit &lt;name&gt; Edit a server in $EDITOR . A draft that is not a valid server is kept rather than saved. Tools pick the change up the next time they start it. mcp delete synapse mcp delete &lt;name&gt; --confirm Take a server out of every tool Synapse gave it to, forget its vault, and remove it from the library. A tool's own entry by that name is left alone. mcp secret synapse mcp secret &lt;name&gt; &lt;VARIABLE&gt; Store one of a server's secrets in its own vault, read from a prompt or stdin, and point the server at it. mcp install synapse mcp install [name] [--tool &lt;tool&gt;] [--replace] Give library servers to connected tools through each tool's own mcp add , as synapse mcp run <name> . A tool's own server by the same name is left alone unless --replace is given; a server that still needs something is skipped. mcp uninstall synapse mcp uninstall &lt;name&gt; [--tool &lt;tool&gt;] Take a server back out of the tools Synapse gave it to. It stays in the library. mcp status synapse mcp status [name] [--json] Show where each server stands in each tool — installed, update available, not ours, not installed — and the servers tools have that the library does not. mcp adopt synapse mcp adopt &lt;name&gt; [--tool &lt;tool&gt;] Move a tool's own server into the library, every value it carries into the vault. The tool's entry is unchanged until you install over it with --replace . mcp search synapse mcp search &lt;words&gt; [--json] Search every source that holds MCP servers — registries and repositories — with what Synapse can run first. A registry is slow; allow half a minute. mcp get synapse mcp get &lt;source:id&gt; [--name &lt;name&gt;] Add a registry entry to the library as the command that runs it — npx , uvx , or docker run — with anything it needs left to fill in. mcp run synapse mcp run &lt;name&gt; What a tool starts. Reads the library and the vault, then replaces itself with the server. Writes nothing to stdout of its own. skill search synapse skill search &lt;words&gt; [--json] Search every source that holds skills at once — skills.sh and any repository you added — and print each result as source:id with its installs or description and its page. A source that cannot answer is reported beside the rest. skill get synapse skill get &lt;source:id&gt; Bring a published skill into the library, proposed. It reaches no tool until skill approve . A bare owner/repo/skill means the first skills index. Refused if the library already has a skill by that name. source list synapse source list [--json] List where skill and MCP searches look: each source's kind, what it holds, where it is, and whether it is built in, yours, or this project's — and whether that project's file is approved. source add synapse source add &lt;name&gt; &lt;owner/repo | address&gt; [--kind &lt;kind&gt;] [--token &lt;vault.name&gt;] Add a skills index, MCP registry, or GitHub repository. The kind is worked out — a repository needs no request, an address is asked — unless --kind says. A private repository names its token in the vault. source remove synapse source remove &lt;name&gt; Stop searching a source, built-in or yours. A project's sources are edited in its .synapse/sources.toml . source allow synapse source allow Approve this project's .synapse/sources.toml as it reads now. Until then its sources are listed and not searched, and editing it takes the approval away. source refresh synapse source refresh [name] Read a repository source's catalogue again now rather than within the hour. skill revert synapse skill revert &lt;name&gt; [&lt;id&gt;] Put an earlier version back, in the library and in every tool holding a copy Synapse wrote. What it said is kept as a new revision, so a revert can itself be reverted. Data and settings data check synapse data check [--json] Open the database, run page and foreign-key integrity checks, apply supported migrations, and report the path, schema version, and ok integrity state. data export synapse data export <file> Create and validate a consistent SQLite snapshot of brain.db at a destination that does not already exist. Secret values live in a separate store — Keychain, or vault.db beside it — and are never in the snapshot. data restore synapse data restore <file> Validate a current-version snapshot and restore it while the app and MCP servers are closed. Preserves the previous database as a recovery backup and refuses without the exclusive lock. settings show synapse settings show Print the active recall optimization, result limit, character budget, supported shell modes, and zsh hook example. settings optimize synapse settings optimize <full|balanced|lean> Change the shared MCP recall response budget. Stored memory is not modified. settings mesh synapse settings mesh &lt;on|off&gt; Turn the agent mesh tools on or off. Connected tools pick the change up the next time they start. settings price synapse settings price &lt;dollars&gt; The price per million input tokens that synapse savings and the Savings page use for the dollar figure. Defaults to $2. settings workers synapse settings workers &lt;count&gt; The most background workers one session may run at once. Every worker is a separate session on the account you already pay for, so this is a spending limit as much as a performance one. Clamped to a built-in ceiling, so a mistyped number cannot buy an unbounded fleet. A supervisor already running picks the change up on its next spawn. settings learn synapse settings learn &lt;on|off&gt; Let agents write and correct skills, adding teach and revise to every connected session. Off by default. A skill an agent writes waits in skill proposed until you approve it, so turning this on never changes how a session behaves without you having read what changed it. guidance show synapse guidance show [--json] Print the SOUL.md path, whether it exists, pointer coverage, and whether both global instruction files are pointer-only. guidance sync synapse guidance sync Create SOUL.md when needed and refresh managed pointers in both global instruction files without removing unmanaged text. guidance adopt synapse guidance adopt --confirm Move unmanaged global guidance into SOUL.md, replace both global files with managed pointers, and retain backups. Tools Synapse does not ship Codex, Claude Code, pi, and Ainz are ordinary descriptors, not special cases. A descriptor is a TOML file saying where a tool keeps its files, what to run against its own CLI to connect it, how to read that back, and which flags it takes when Synapse starts it. Yours resolve from .synapse/tools/ in a repository first, then your data directory, then the ones Synapse ships — so a project can carry the tool its team works in, and you can correct a built-in without waiting for a release. A described tool gets everything a built-in gets: connection, shared guidance, the skill library, and the mesh. Follow the custom harness guide for a descriptor example, setup verification, and contribution instructions. tool list synapse tool list [--json] Every tool this machine can connect to, with the layer each one resolves from. tool show synapse tool show &lt;name&gt; Print one descriptor and where it came from. tool create synapse tool create &lt;name&gt; Describe a tool Synapse does not ship. Opens a commented template in your editor and refuses to save a file that would not load. The name becomes the descriptor's file name and what you pass to --tool . tool edit synapse tool edit &lt;name&gt; Edit a descriptor. Editing one Synapse ships copies it into a layer you own first, so the shipped file stays as it was. tool delete synapse tool delete &lt;name&gt; Remove a descriptor you added. Deleting a copy that overrides a built-in returns you to the shipped one. Installation and paths install synapse install Install the current executable for this user. A packaged app creates a launcher into the signed bundle; a development binary is copied atomically. Unrelated destination files are never overwritten. path synapse path Print the resolved data directory, SOUL.md, and CLI destination. savings synapse savings [--days &lt;n&gt;|--all] [--json] Tokens and dollars Synapse kept out of your sessions, compared with keeping the same memory in an instruction file every session loads. Counts what each session could reach, what recall and the session hook actually sent, and what Synapse's own tool definitions and guidance cost. Defaults to the last 30 days. Figures are characters divided by four, counted once per session, so they understate. doctor synapse doctor [--json] Report everything a bug report needs: version, store state and size, drift found from the current folder, connected tools and what each is set up with, skill and mesh state, shell and CLI integration, resolved paths, and recent crashes. Every check reports rather than fails, so a broken store is described instead of stopping the report. Nothing is sent anywhere. connect synapse connect [tool] Wire a tool into memory and the vault: register the Synapse MCP server through that tool's own CLI, and point its global instruction file at SOUL.md. With no name it connects every tool this machine has. An existing registration that already points at Synapse is left alone. Synapse never edits a tool's configuration itself — it asks the tool to. connect --refresh synapse connect &lt;tool&gt; --refresh Apply this release's descriptor to a tool that is already connected, writing its registration again. Detection cannot tell a descriptor that moved from one that did not, so a tool set up under an older release keeps that release's answer until this is run. Nothing is removed and the tool stays connected throughout. synapse doctor reports update available when it is worth running. connect --reset synapse connect &lt;tool&gt; --reset Disconnect the tool and connect it again. Costs more than --refresh : disconnecting takes back the skills Synapse installed for that tool, and this restores the connection rather than the library. For when what is on disk has to go before the new answer can land. disconnect synapse disconnect [tool] Undo one tool's connection, or every tool's when no name is given: the MCP registration or installed package, the managed block in its instruction file, the Claude Code hooks and status line, and any skill Synapse installed for it. A skill you wrote, or a status line somebody else configured, is left alone. uninstall synapse uninstall [--data] [--confirm] Remove everything Synapse installed: every tool connection, the shell hook, and the command line tool. Without --confirm it prints what it would remove and stops. Your memory is left alone unless you also pass --data , which cannot be undone. version synapse version Print the application version. --version and -V are aliases. help synapse help [command] Print the command summary, or one command’s entries. --help and -h do the same thing wherever they appear before a -- ."},{"title":"Troubleshooting","description":"Diagnose installation, tool connection, scope, vault, memory, database, and restore failures without risking user-owned data.","path":"docs/troubleshoot/","text":"Start with status shell Copy synapse version synapse path synapse data check synapse status . synapse settings show These commands establish which executable is running, which database it opens, whether the database is healthy, which scopes apply to the current folder, and which response budget connected tools use. One command for a bug report synapse doctor gathers everything anyone would ask you for: version, store state and size, which tools are connected and what each is set up with, skill and mesh state, shell and CLI integration, resolved paths, and any crash Synapse has recorded. shell Copy synapse doctor synapse doctor --json Every check reports rather than fails, so a damaged store is described in the report instead of ending it. Synapse sends nothing anywhere — the report is printed for you to read or paste. The desktop application writes panics to <code>crash.log</code> in the data folder, because a window that disappears leaves nothing to read. The log holds only the crash itself and is bounded, so a crash loop cannot fill a disk. <code>doctor</code> prints the most recent entries. undefined App and CLI macOS will not open the app Confirm you downloaded the current archive from the release page, extracted it fully, and moved the app to Applications. The app is Developer ID signed and notarized, so a Gatekeeper refusal usually means the archive was altered or only partly extracted. Avoid modifying files inside the application bundle because that invalidates its signature. synapse: command not found Install the CLI from Settings, then add ~/.local/bin to PATH . Run the executable by full path once to verify: shell Copy ~/.local/bin/synapse version The CLI installer reports a conflict A different file already exists at the destination and does not have a matching Synapse receipt. Synapse will not replace it. Move or rename that file yourself, or set SYNAPSE_BIN to a different full destination and install again. The CLI stopped working after moving the app Packaged installations point into the signed bundle. Open Synapse at its final location and choose Install CLI again to refresh the managed launcher. Tool connection A tool is detected but not connected The registered entry may point to a deleted build or old app location, or may have arguments other than [\"mcp\"] . Choose Repair in the app. Synapse backs up the integration store before replacing the named entry. The tools do not appear after connection Restart the developer tool so it relaunches user-level MCP servers. Confirm the stored command exists and runs synapse mcp without printing shell startup text to stdout. The tools follow different global guidance Run synapse guidance show . If a pointer is missing, use synapse guidance sync or Settings → Shared guidance → Sync pointers , then restart the tools. Consolidation is optional and requires separate confirmation. Setup failed Read the app error for the external tool command that failed. Setup restores both the integration store and instruction file on failure, leaving .synapsebackup recovery copies beside changed existing files. Scopes and secrets vault scope is not ready Run synapse status . . Approve pending or changed files with synapse allow only after inspecting them. Fix invalid YAML and unknown vault.name references before trying either shell mode again. The shell hook does not activate Open Settings and check Shell environments . Enable or repair the detected hook, then open a new terminal. Run synapse status . ; it should report the current shell hook and ambient state as ready. Approve the closest scope with synapse allow . Global mappings alone never activate ambient mode. Settings reports an incomplete or duplicate hook block Synapse refuses to guess which startup-file content it owns. Open the path shown in Settings, reduce the # synapse:shell:begin and # synapse:shell:end markers to one complete pair or remove that marked block, then return to Settings and enable the hook again. An ambient value remains after leaving Press Enter once so the prompt hook reevaluates the directory. If the name existed before activation, Synapse deliberately restores that original value. Run synapse status . to distinguish a restored value from an active scope. A narrower mapping does not apply Check every ancestor scope. A broader deny permanently blocks that environment name for narrower scopes. Also confirm the nested file itself is approved and the command’s working directory is below it. Keychain access fails This applies on the Keychain store only; run synapse vault backend to see which one this machine uses. Unlock the login Keychain and retry from an interactive user session. If a label exists but its item was removed externally, set the secret again. If the credential itself may be invalid, rotate it at its issuer before storing the replacement. A denied Keychain prompt during vault migrate stops the migration with nothing moved and nothing switched, so retrying after granting access is safe. A secret has no value in the encrypted vault The label is in brain.db and the sealed value is not in vault.db . That happens when the two came from different machines, or when a value was set while the machine was on the other store. Run synapse vault backend to confirm which store is in use, synapse vault migrate to bring values across from the other one, or synapse secret set to write the value again. Nothing opens with the vault key vault.key is the only thing that opens vault.db , and a replaced key cannot be recovered from the sealed values. If the key was lost, set each value again with synapse secret set ; the labels and mappings in brain.db tell you which ones are expected. Back the two files up together, and treat that backup like the credentials it holds. The child sees a value but MCP does not This is expected. MCP reports only names, trust state, and which store this machine uses. Values are read out of the vault only for synapse run -- <command> , while an installed shell hook activates an approved directory, or when you ask for one with synapse secret copy . Memory and database Recall returns too little content Run synapse settings show . Switch to Balanced or Full if Lean is too small. Search with concrete words present in the stored body and inspect the exact entry through synapse memory show <id> . A memory that is definitely stored does not come back Make the search show its working with synapse memory list \"your query\" --explain . It prints the words it searched for and the words it dropped for matching nearly every memory, which separates a query that lost its only real term from a store that holds nothing. If what you are looking for is an exact string — a flag, an identifier, a path — use synapse memory grep instead, which matches characters and never drops a word. Also check the entry has not been superseded: synapse memory list marks a replaced memory, synapse memory show <id> names what replaced it, and synapse memory restore <id> puts it back in recall. A stored memory is wrong If the wording was bad, edit the original. If it was true and stopped being true, add the new version and run synapse memory supersede <old> <new> — recall returns the new one and the old text stays readable. If it should never have been stored, delete it. What to avoid is adding a contradictory entry and leaving both live, because recall returns both. Export a snapshot before a large cleanup. An import shows flagged entries Open the provider folder with Review source and inspect the named files. The app never imports flagged content. If a CLI import is genuinely safe, rerun it with both --include-flagged and --confirm ; otherwise move only the durable non-sensitive fact into Synapse manually. One project's memory appears missing Inspect the record's project in the Memory editor or with synapse memory show <id> , and compare it with what the current folder resolves to. A repository with an origin remote is keyed by that remote, such as github.com/owner/repo ; anything else is keyed by its root folder's path. Project recall intentionally combines global memory with one matching project and excludes every other project. Two cases account for most misses. A folder with no remote that was moved or renamed has a new path, so memory stored under the old one no longer matches: add a remote, or edit the record's project to the new path. And memory filed under a folder that no longer exists cannot be recalled from anywhere until its project is edited to a current folder or key. A stored memory says [redacted] Synapse removed a credential from it before storing: a value assigned to a secret-sounding name, a password in a URL, a bearer token, a provider token, or a private key. That is deliberate, and the value was never written to the database. Put the value in the vault and let the memory say where it lives. Anything between <private> and </private> is removed the same way. Search returns a memory that only shares part of a word Search matches word forms and three-character fragments as well as whole words, so schemas can find schema , and a long term can match inside a longer identifier. A memory that matches both ways ranks above one that matches only one way. Run synapse memory list <query> --explain to see both expressions and each hit's score, and synapse memory grep when you want exact characters only. Integrity check fails Stop using the affected database. Do not overwrite it. Preserve the whole data directory, locate the newest known-good export or automatic backup, and follow the restore procedure. A failed check is a data-recovery event, not a migration prompt. Restore Restore says Synapse is using the database Quit the desktop app and close every developer-tool session connected to the MCP server. Confirm no synapse mcp process remains, then retry. The refusal proves the exclusive lifecycle guard is working. The backup version is unsupported The source schema must match the current release. Keep the source unchanged. Open it first with the Synapse version that created it, let that version migrate normally if appropriate, export a fresh snapshot, then restore that compatible export with the current app. Secrets are missing after moving to another machine Database exports carry brain.db and never a value. Recreate each value with synapse secret set on the destination, or — on the encrypted store — copy vault.db and vault.key across together, since neither is any use without the other. Existing vault labels and references in the restored database tell you which entries are expected."},{"title":"Tutorials","description":"Eleven guided walkthroughs arranged by how far into Synapse you are, from a first connection to running agent teams and removing everything cleanly.","path":"tutorials/","text":"These are arranged by how far into Synapse you are, not by feature. Each level assumes the one before it, and each tutorial ends by pointing at the next. If you are new, start at the top and stop whenever Synapse is doing what you need — most people never leave the second level, and that is a complete way to use it. Level 1 · Newcomer You have downloaded Synapse and want one tool remembering things. Roughly an hour for both. Install and connect your first tools Start from the release archive, install the CLI, connect Codex, Claude Code, or pi, and verify all three MCP tools from the app, the terminal, and the tool itself. Carry one decision between tools Store a confirmed convention in one tool, end the session, recover it in another, and inspect the exact record that made it across. Level 2 · Daily driver Synapse is connected and you use it every day. This level is about controlling what it remembers, what it can hand to a command, and what your tools know how to do. Curate and optimize memory Add, find, correct, delete, and budget durable memory using both human-readable and JSON output. The one to read if recall is returning too much or the wrong thing. Use a scoped secret in either shell mode Create a vaulted value, compare one-command and ambient loading without printing the value, and watch trust invalidate when the file changes. Keep one skill library across every tool Write an Agent Skill once, install it into every connected tool, and see how Synapse tells a library that moved on from a copy somebody edited by hand. Let your agents write skills Let a session write down a procedure it worked out, prove for yourself that it reaches no tool until you approve it, then correct it and take the correction back. Level 3 · Team operator You want several agents working at once on one job. This is the deepest level, and the one where agents touch real files on your machine — use a checkout you can restore. Start a tool with everything in place Open a tool with memory, scoped credentials, and the project root already wired, without writing anything into that tool's own configuration. Run a team of agents and drive it yourself Turn on the mesh, open a team, read what every agent is doing from one terminal, answer a worker that gets stuck, and shut it all down cleanly. Hand a job to one agent and watch it grow a team Describe an outcome instead of picking a roster, answer the question only you can answer, and reach a worker directly without going through the agent that started it. Level 4 · Maintainer You are responsible for the store staying sound, or you are handing the machine on. Read these before you need them. Export and restore safely Create a validated snapshot, make a reversible change, acquire the exclusive lifecycle lock, restore, and verify recovery. Check, migrate, and remove Synapse Read a full health report, understand what a schema migration does, disconnect one tool, and remove everything while keeping your memory and your own files. What every tutorial includes Each one names its prerequisites, builds one complete outcome, includes verification you can observe rather than assume, calls out the security boundary it crosses, and ends with cleanup or the next safe step. Commands assume the installed synapse CLI is on PATH . If you need exact command syntax outside a guided workflow, use the CLI reference . If a step fails, stop there and use Troubleshooting rather than skipping a trust or integrity check — those checks are the reason the destructive steps later are safe."},{"title":"Install and connect your first tools","description":"Go from the signed release archive to a working local MCP connection, and verify the same memory from the desktop app, the terminal, and the coding tool itself.","path":"tutorials/connect/","text":"Outcome and prerequisites You will install Synapse, install its command-line tool, connect at least one coding tool, store one harmless memory, and see that same record through three separate surfaces. By the end you will also know exactly which files were touched and how to take every one of them back. For this desktop walkthrough: an Apple-silicon Mac running macOS 13 or later. Linux users can follow the terminal setup guide , then try a memory handoff . Codex, Claude Code, or both installed and on PATH . The latest synapse.zip release archive. About twenty minutes. Move the app somewhere permanent, then open it Extract synapse.zip , move synapse.app to Applications, and open it. The app is Developer ID signed and notarized, so it opens without a security override and without a right-click workaround. Choose the location before the next step The CLI installed from a packaged app launches the executable inside that signed bundle. Install the CLI only after the app is where you intend to keep it, or the command will point at a path that no longer exists. If you move the app later, the dashboard offers Repair — it recognizes a stale executable path rather than reporting a healthy connection. Install the CLI Open Synapse Settings and choose Install CLI , or run the installer from inside the bundle. Either way it reports where it landed: text Copy Installed /Users/example/.local/bin/synapse Add /Users/example/.local/bin to PATH to use synapse from your shell. Open a new terminal and confirm the shell can find it: shell Copy synapse version synapse path text Copy synapse 0.1.0-beta.24 text Copy data /Users/example/Library/Application Support/synapse soul /Users/example/Library/Application Support/synapse/SOUL.md cli /Users/example/.local/bin/synapse If the shell cannot find synapse , add ~/.local/bin to PATH and start another shell. The data path above must match what the app reports on its Settings screen — if the two differ, something has set SYNAPSE_DATA in one context and not the other, and they are reading different stores. Connect a detected tool Return to the dashboard. Each supported tool appears as a row with its state. Choose Connect beside Codex, Claude Code, or pi, and the row moves from detected to connected. Connect the others too if you have them — the whole point is one memory behind all of them. Detection is not a guess. Synapse parses the actual entry named synapse in the tool's own configuration and reports connected only when the stored command resolves to the expected executable and its arguments are exactly [\"mcp\"] . A tool whose binary was deleted or moved reads as stale, not healthy. Know what connecting actually wrote This is worth understanding now rather than the first time something looks wrong. Connecting is not a black box — it is four specific changes, and every one is reversible. Change Where How it is made The MCP server ~/.codex/config.toml or ~/.claude.json Through the tool's own mcp add command, not by editing its file directly. A guidance pointer ~/.codex/AGENTS.md or ~/.claude/CLAUDE.md Appended inside a managed block. Your own text is untouched. A session hook ~/.claude/settings.json Claude Code only. Runs synapse session at session start. A status line ~/.claude/settings.json Claude Code only, and only if you do not already have one. Yours is reported, never replaced. Every changed file gets a .synapsebackup sibling before it is replaced, and the whole operation rolls back if any step fails. Check the shared guidance file was created: shell Copy synapse guidance show text Copy SOUL.md /Users/example/Library/Application Support/synapse/SOUL.md exists true pointers 2/2 consolidated false A full count — pointers 3/3 with all three tools connected — means each one points at a single editable SOUL.md . That file is yours to edit; it is where shared guidance for every connected tool lives. Restart the tool and read its first line Close and reopen the connected tool so it loads the new MCP server. A running session keeps the tool list it started with, so this step is not optional. Claude Code will print a line beside its welcome box before the model has written anything: text Copy Synapse connected · no memories yet That line comes from the session hook, which is the only way to state the connection before the first reply. It also hands the session this project's memory directly, so a Claude Code session starts already holding what the project has decided rather than being asked to go and look. On an empty store there is nothing to hand over yet, which is what you are seeing. Codex has no session hook, so it reports the connection in its first reply instead, following the guidance in SOUL.md . Either way, ask the tool what Synapse tools it has. The answer should include remember , recall , readmemory , and vaultstatus — four core tools. The sixteen mesh tools appear only when you switch the mesh on. Store one memory Tell the connected tool: Remember this confirmed tutorial convention: documentation examples use the source label synapsetutorial. It should call remember with the current project root and report a numeric ID. Use an ordinary convention for this — memory is plain text readable by every connected tool, so never put a token, password, or private key in it. Synapse removes the ones it recognizes before storing and replaces them with [redacted] , but that is a backstop, not a reason to try. Verify the same record three ways One store, three surfaces. Check all of them, because a mismatch here is the clearest signal that something is pointed at the wrong place. shell Copy synapse memory list synapsetutorial text Copy 1 project:/Users/example/project synapsetutorial Documentation examples use the source label synapsetutorial. The columns are the ID, the scope and project it belongs to, the source label, and the body. Now the exact record: shell Copy synapse memory show 1 text Copy Memory #1 Scope: project Project: /Users/example/project Source: synapsetutorial Created: 1785776940 Documentation examples use the source label synapsetutorial. Finally, open the Memories screen in the app and search for synapsetutorial . The body, source, and scope must match what the terminal just printed. At this point the coding tool, the CLI, and the desktop app are provably reading one local database. If something is not right Symptom What it usually means The tool lists no Synapse tools It was not restarted after connecting. Close it fully and reopen. The dashboard row says stale The app moved after the connection was made. Choose Repair . synapse : command not found ~/.local/bin is not on PATH , or the shell has not been restarted. CLI and app show different memory counts They are on different data directories. Compare synapse path with the app's Settings screen. The tool reports a connection that is not there Run synapse doctor . It reports what is actually configured rather than what should be. shell Copy synapse doctor That one command is also what to attach to a bug report. It carries no memory contents and no secret names, so it is safe to paste into a public issue. Undoing all of it shell Copy synapse disconnect claude # or codex, or nothing for every tool Disconnecting removes only what Synapse wrote: the MCP entry, the managed block in the instruction file, the session hook, and the status line — and only if that status line is the one Synapse installed. Your own words in CLAUDE.md , your own status line, and every memory survive it. Removing memory is a separate, explicit decision covered in Check, migrate, and remove Synapse . Next step Continue with Carry one decision between tools to prove a real handoff across a session boundary and a tool boundary."},{"title":"Carry one decision between tools","description":"Make a real project convention durable in one session, recover it in another tool after closing the first, correct it at the source, and see the scope rule keep other projects out.","path":"tutorials/continuity/","text":"Outcome and prerequisites You will prove that durable context survives two boundaries a conversation cannot: the end of a session, and the move to a different tool. Then you will correct it at the source and confirm that another project cannot see it. Install and connect your first tools completed. Two connected tools make the handoff visible. Two separate sessions of one tool prove persistence just as well. A real project folder, and a second one for the scope check. Choose one decision worth keeping Use something concrete, harmless, and genuinely load-bearing for future work. A package-manager choice, a supported deployment target, a naming rule, or a correction to an assumption that turned out wrong are all good. The test of a good memory is whether a session that did not know it would do something different. Worth remembering Not worth remembering \"Use Bun for JavaScript tasks in this repository.\" \"The user asked me to check the tests.\" \"Releases are triggered by a version bump on main.\" \"The build is currently failing.\" \"Do not hand-edit the generated site directory.\" A full transcript of how you reached the decision. The right-hand column is transient. It is true today and misleading next week, and a memory store full of it makes recall worse rather than better. Never stage a fake secret Memory is plain text available to every connected tool. Use an ordinary project convention, never a token, credential, or private key — not even a made-up one, because a habit formed here is a habit that shows up in a real store. Confirm it, then have the tool remember it In the first connected tool, establish the decision explicitly, then ask for it to be stored: We have confirmed that this repository uses Bun for JavaScript tasks. Remember that durable convention with source synapsetutorial. The tool should call remember once, with project scope and this repository's root, and return an ID. Two things can go wrong here and both are worth noticing: It stores a transcript instead of the decision. A memory should be one durable idea in one or two sentences. You will fix this in the correction step. It stores global scope instead of project. Global memory is returned in every project, which is right for a preference about how you like to work and wrong for a fact about one repository. Close the session completely Exit the first tool. Not a new conversation inside it — exit it. This is what rules out conversational context and leaves the local database as the only thing carrying the decision forward. Recall it from the other tool Open the second connected tool in the same repository. If it is Claude Code, look at what happens before you type anything: the session hook has already recalled this project's memory and handed it over, so the convention is in context from the first turn. That is the difference between memory that works and memory that depends on the model remembering to ask. Either way, ask a question the convention should change the answer to: What command should I use to install dependencies here? It should answer with Bun rather than npm, and be able to say where that came from. If it guesses wrong, ask it explicitly to recall the JavaScript tooling convention for this project — that tells you whether the memory is missing or merely was not consulted. Inspect the source of truth and correct it What the tool reported is a rendering. Go and look at the record itself: shell Copy synapse memory list "Bun JavaScript" text Copy 4 project:github.com/example/project synapsetutorial We confirmed together that this repository uses Bun for JavaScript tasks rather than npm, after discussing… shell Copy synapse memory show 4 If the body is a paragraph of conversation rather than a durable statement, replace it. Editing keeps the same ID, so nothing else has to change and no contradicting second record is created: shell Copy printf '%s\\n' 'Use Bun for JavaScript tasks in this repository.' \\ | synapse memory edit 4 synapsetutorial text Copy Updated memory #4 Start one more new session and ask again. The answer should now reflect the corrected text — which proves that editing changed the durable source rather than layering a correction on top of a wrong record. Correcting beats appending Two memories that disagree are worse than one that is slightly wrong, because recall returns both and the model has to guess which is current. When the wording was bad, edit the record. When the old version was true and stopped being true, add the new one with synapse memory supersede so the old text stays readable but out of recall. Add a plain second memory only when it is genuinely a new fact. Prove other projects cannot see it This is the property that makes a shared store usable. Open a session in an unrelated project and ask the same question: What command should I use to install dependencies here? It must not answer with the first project's convention. Confirm from the terminal that the record is scoped where you think it is: shell Copy synapse memory show 4 text Copy Memory #4 Scope: project Project: github.com/example/project Source: synapsetutorial Recall from any project returns everything global plus everything stored for that project. Another project's memory never enters the response. The project root is resolved by walking up for a .git directory or a .synapse.yaml , so a session opened in a subdirectory still resolves to the same root — which is why the same memory is found whether you start at the repository top or three folders down. The project is then named by the repository's origin remote, which is why it reads github.com/example/project rather than a folder. Clone the repository somewhere else, or open a worktree of it, and a session there recalls the same memory. A folder with no remote is named by its path instead, and memory filed under it stays with that path. Store one thing globally, and see the difference Some things belong everywhere. A preference about how you like to work is not a fact about one repository: shell Copy printf '%s\\n' 'Prefer small, focused commits with imperative subject lines.' \\ | synapse memory add synapsetutorial --global text Copy Stored memory #5 Now recall from both projects. The global preference appears in each; the Bun convention appears in only one. That split — global plus this project, never another project — is the entire scope model, and it is enforced in the query rather than trusted to the caller. Keep or clean up If the convention is real, keep it — you have just made your tools better at this repository. If it was tutorial-only, remove the exact IDs: shell Copy synapse memory list synapsetutorial synapse memory delete 4 --confirm synapse memory delete 5 --confirm Deleting requires --confirm and names the record it is about to remove. Avoid synapse memory wipe unless you genuinely intend to remove every memory in the store; it exists for starting over, not for tidying. Next step You have finished the newcomer level. Continue with Curate and optimize memory , which is about keeping a store useful once it has more than a handful of entries in it."},{"title":"Curate and optimize memory","description":"Build a small memory set, learn how search actually behaves, correct entries at the source, tune the recall budget, import existing notes reversibly, and remove only what you added.","path":"tutorials/curate/","text":"Outcome and prerequisites You will build a small set of entries, understand why search returns what it returns, correct one at the source, compare what each recall budget actually sends to a model, import a Markdown file reversibly, and then undo it. This is the tutorial to read when recall is returning too much, too little, or the wrong thing. The synapse CLI installed and at least one tool connected. A project folder. Steps here create and delete entries under one source label. Write down the IDs Every add prints its new numeric ID. Keep those numbers for the cleanup step. Do not reach for synapse memory wipe in a store that has real context in it — it removes everything, and it is meant for starting over rather than tidying. Add focused entries Content is read from stdin, never from an argument, so a memory can contain anything without shell quoting getting in the way: shell Copy printf '%s\\n' 'Tutorial apps use port 4100.' | synapse memory add synapsetutorial printf '%s\\n' 'Tutorial commands use Bun.' | synapse memory add synapsetutorial printf '%s\\n' 'Tutorial backups go in the backups folder.' | synapse memory add synapsetutorial text Copy Stored memory #1 Stored memory #2 Stored memory #3 synapsetutorial is the source — a label saying where the fact came from. Give every memory one. It is what lets you find a set later, audit what a particular session stored, and delete a batch without hunting. One durable idea per entry. Three entries about ports, runtimes, and backups can each be recalled, corrected, and deleted on their own; one entry containing all three is returned whole every time any part of it is relevant, and cannot be corrected without rewriting the rest. Search and inspect shell Copy synapse memory list tutorial text Copy 2 project:/Users/example/project synapsetutorial Tutorial commands use Bun. 1 project:/Users/example/project synapsetutorial Tutorial apps use port 4100. The columns are the ID, the scope with its project, the source, and the body. For anything scripted, ask for JSON instead: shell Copy synapse memory list "Bun" --json text Copy [ { "id": 2, "body": "Tutorial commands use Bun.", "source": "synapsetutorial", "scope": "project", "project": "/Users/example/project", "created": 1785776940 } ] And for one exact record, with nothing trimmed: shell Copy synapse memory show 2 text Copy Memory #2 Scope: project Project: /Users/example/project Source: synapsetutorial Created: 1785776940 Tutorial commands use Bun. Understand how search behaves Recall is full-text search with a few behaviors worth knowing, because each explains results that otherwise look wrong. Words match in other forms, and so do fragments. The query is searched twice: once by word, stemmed, so deploying finds a memory that says deploys ; and once by three-character pieces, so hashpw finds scripts/hashpw.sh . A memory that matches both ways ranks above one that matches only one. Its source is searched too, at a lower weight, so the label you file a memory under helps find it. shell Copy synapse memory list deploying synapse memory list synapsetutorial Common words are dropped. A query is stripped of words that carry no meaning on their own. Try it: shell Copy synapse memory list "what are the" Nothing in that query survives, so instead of matching every memory containing the , Synapse answers the way it answers an empty query — with the most recent entries. That is deliberate. Matching on a common word is both wrong, because a memory that happens to contain are is not the answer to anything, and slow, because a common word makes the index rank most of the store. An empty query is a real question. It means \"what should I know here\", and it returns the most recent memory in scope. This is exactly what a session opening asks, and it is what the Claude Code session hook uses. shell Copy synapse memory list Words that carry a preference are kept. not , never , and use look like function words but change the meaning of a convention completely, so they stay in the query. \"Never use npm\" and \"use npm\" must not search identically. You can ask what it did. When a memory you know is stored does not come back, the fastest answer is to make the search show its working: shell Copy synapse memory list "what are the credentials" --explain text Copy Query: what are the credentials Mode: search Expression: "credentials" Fragments: "credentials" Searched: credentials Dropped: what, are, the (matches nearly every memory) Matches: 0 That distinguishes the two things that look identical from the outside: a query that lost its only real word, and a store that genuinely holds nothing. And you can skip the ranking entirely. When you want an exact string — a flag, an identifier, a path — the ranked search is the wrong tool, because the thing you are looking for may be a word it drops: shell Copy synapse memory grep -- --no-verify And you can measure it. Once a project has memory worth keeping, write down a few questions you expect it to answer, with text the right answer contains, and score recall against them. Run it again after a cleanup and the numbers say whether the store got easier to search or harder: toml Copy [[case]] query = "which javascript runtime" project = "/Users/example/project" expect = ["bun"] shell Copy synapse memory eval cases.toml Measuring recall explains each number, including how often the answer survived the response budget. Scope is applied in the query, not after it Every search returns everything global plus everything for the current project, and nothing from any other project. That is enforced where the rows are selected rather than filtered afterwards, so there is no path where another project's memory is read and then discarded. Correct at the source A convention becomes more precise. Replace the record rather than adding a second one that disagrees with it: shell Copy printf '%s\\n' 'Tutorial commands use Bun unless a task requires another runtime.' \\ | synapse memory edit 2 synapsetutorial synapse memory show 2 text Copy Updated memory #2 The ID is stable, so anything referring to this memory still refers to the right thing, and future recall returns only the corrected text. Two entries that contradict each other are the single most common way a memory store gets worse as it grows: recall returns both and the model has to guess which is current. Editing is right when the old wording had nothing worth keeping. When the old version was true and then stopped being true , supersede it instead — the new memory becomes the one recall returns, and the old one stays readable as the record of what used to be the case: shell Copy printf '%s\\n' 'Tutorial commands use Bun. Node is used only for the release script.' \\ | synapse memory add synapsetutorial synapse memory supersede 2 6 text Copy Memory #2 superseded by #6; recall now returns #6 instead Undo with: synapse memory restore 2 Check both. Memory 2 is still there, marked, and out of recall; memory 6 is what a tool now sees. If you decide you were wrong, synapse memory restore 2 puts it straight back. shell Copy synapse memory show 2 synapse memory list Tune what recall actually sends The budget controls how much a recall response costs a model in context. Look at where you are: shell Copy synapse settings show text Copy optimization balanced result limit 8 character budget 6000 mesh off shell modes command-scoped, ambient directory zsh hook eval "$(synapse hook zsh)" Budget Results Characters When to use it Full 25 unbounded A small store, or when you want everything and context is cheap. Balanced 8 6,000 The default, and the right answer for most stores. Lean 4 2,800 A large store, a long session, or a model whose context you are protecting. shell Copy synapse settings optimize lean text Copy Recall optimization set to Lean Ask a connected tool to recall synapsetutorial and note how many entries come back. Switch to full and ask again. The stored bodies never change — synapse memory show returns the same text at every setting. The budget only shapes the response. A tool can request a smaller budget for one call, and that request can only ever shrink your configured ceiling. A session cannot talk its way into a larger response than you have allowed. shell Copy synapse settings optimize balanced Import notes you already have If you have been keeping conventions in a Markdown file, bring them in. Every import previews first: shell Copy synapse memory import markdown notes.md text Copy Markdown: 1 found · 1 ready · 0 existing · 0 flagged ready notes.md # Conventions - Use Bun, never npm. - Deploys target Apple silicon only. … Preview only. Add --confirm to import safe entries. Now try one with a credential in it and watch what happens: text Copy Markdown: 1 found · 0 ready · 0 existing · 1 flagged review flagged.md Content hidden until the source file is reviewed. warning: mentions a credential-shaped environment variable Preview only. Add --confirm to import safe entries. Flagged content is not merely skipped — it is not even displayed, because printing it to your terminal to warn you about it would defeat the point. --confirm imports the ready entries and leaves flagged ones untouched. The flagging is conservative, not exhaustive It looks for private-key markers, credential-shaped variable names, assignments to password- and token-like keys, and bearer tokens. A secret that matches none of those patterns will not be caught. Read your own preview — the flag is a safety net, not a substitute for looking. shell Copy synapse memory import markdown notes.md --confirm text Copy Import batch #1: 1 stored, 0 linked, 0 already imported, 0 flagged and left untouched Undo with: synapse memory undo 1 --confirm The entries that are imported are redacted on the way in, like every memory Synapse stores: a credential that slipped past the flag becomes [redacted] rather than being written to the database. Imports are idempotent. Running the same file again reports the entries as already imported rather than creating duplicates, so a repeated import is harmless. Claude and Codex memory stores import the same way with synapse memory import claude and synapse memory import codex . Undo the whole batch shell Copy synapse memory imports text Copy 1 markdown 1 stored 0 linked active shell Copy synapse memory undo 1 --confirm synapse memory imports text Copy Undid import batch #1; removed 1 imported memories text Copy 1 markdown 1 stored 0 linked undone The batch is recorded rather than forgotten, so an import you regret is one command to reverse and the record of it having happened survives. Anything you wrote by hand is untouched — undo removes only what that batch stored. Delete only what you added shell Copy synapse memory list synapsetutorial synapse memory delete 1 --confirm synapse memory delete 2 --confirm synapse memory delete 3 --confirm text Copy Deleted memory #1 Both destructive commands refuse without --confirm , and say which record they mean: text Copy Error: add --confirm to delete memory #3 Error: add --confirm to delete every memory A final synapse memory list synapsetutorial should return nothing. Use the Memories screen for a visual review before deleting anything you did not create in this tutorial. Next step Continue with Use a scoped secret in either shell mode , which is the other half of what Synapse hands a tool — and the half where the boundaries matter most."},{"title":"Use a scoped secret in either shell mode","description":"Create a vaulted value, approve its project mapping, compare one-command and ambient loading without ever printing it, and watch trust invalidate the moment the file changes.","path":"tutorials/secrets/","text":"Outcome and prerequisites You will create a disposable value in the vault, map it to one project, and exercise both ways Synapse can hand it to something — without the value ever appearing on screen. Then you will break the approval on purpose and watch every path refuse. The synapse CLI installed. If synapse vault backend says keychain , unlock your login Keychain first. An empty temporary folder. Do not run this in a real project. About twenty-five minutes. Where the value actually lives Worth being precise about before you start, because the whole design follows from it. Thing Stored where The secret value The value store, and only there. That is vault.db — sealed, one envelope per secret, under the key in vault.key — or macOS Keychain if the machine is set to it. synapse vault backend says which. Its name, its variable name, its account reference brain.db , Synapse's SQLite database. Never the value. Which variable a folder should get .synapse.yaml in the project, which holds a reference and never a value. Whether that file is trusted A SHA-256 digest of its exact bytes, recorded when you approve it. No secret value is ever written to brain.db , to YAML, to an MCP response, or to a log, and neither store keeps one in plaintext. A value is read out of the vault at the moment a child process is launched, and goes nowhere else unless you ask for it with synapse secret copy , which puts it on the clipboard and still never prints it. Create a vault and a value shell Copy mkdir -p "$HOME/tmp/synapsetutorial" cd "$HOME/tmp/synapsetutorial" synapse vault create tutorial synapse secret set tutorial demo SYNAPSE_TUTORIAL_TOKEN Enter a disposable value at the hidden prompt and confirm it. A vault is an organizational label; the three arguments are the vault, the secret's name inside it, and the environment variable it will become. text Copy Created vault tutorial Saved tutorial.demo in the encrypted vault Now list what exists. Metadata only — there is no command that prints a value, because there is no reason for one to exist: shell Copy synapse secret list tutorial text Copy tutorial.demo SYNAPSE_TUTORIAL_TOKEN scoped Secrets are never command arguments synapse secret set reads from a TTY prompt or stdin, never from an argument. An argument would land in your shell history, in the process table, and in any terminal recording — three places a value should never be. Map it to this folder and approve the file shell Copy synapse scope init . Replace the generated .synapse.yaml with: yaml Copy version: 1 scope: project env: SYNAPSE_TUTORIAL_TOKEN: tutorial.demo deny: [] tutorial.demo is a reference, not a value. This file is safe to commit — that is the point of it holding a reference. Look at the file, then approve its exact bytes: shell Copy synapse scope status . synapse allow synapse status . text Copy Allowed /Users/example/tmp/synapsetutorial/.synapse.yaml text Copy Folder: . Available: SYNAPSE_TUTORIAL_TOKEN Ambient: ready /Users/example/tmp/synapsetutorial/.synapse.yaml [project · approved] Ambient: ready means a shell hook would activate here. It does not mean one is installed. Hand it to exactly one command This is the narrower of the two boundaries and the one to prefer. Verify presence without printing content: shell Copy synapse run -- sh -c 'test -n "$SYNAPSE_TUTORIAL_TOKEN" && echo "tutorial token available"' The child prints only the fixed sentence. Confirm the parent shell was never touched: shell Copy test -z "$SYNAPSE_TUTORIAL_TOKEN" && echo "parent unchanged" Synapse read the stored value, set it on that one child, and the child exited with it. Nothing was exported into your session. The child is fully trusted with the value Any process launched through synapse run can read and disclose what it is given. This tutorial uses a disposable value and a child that only tests whether it is non-empty. The boundary Synapse enforces is which processes receive a value, not what they do with it afterwards. Activate the whole directory For a permanent setup, use Settings → Shell environments → Enable shell hook and open a new terminal. To try it immediately in this shell: shell Copy # zsh eval "$(synapse hook zsh)" # bash: eval "$(synapse hook bash)" # fish: synapse hook fish | source The approved scope activates at once. Now walk in and out of it: shell Copy test -n "$SYNAPSE_TUTORIAL_TOKEN" && echo "ambient token available" cd .. test -z "$SYNAPSE_TUTORIAL_TOKEN" && echo "ambient token unloaded" cd synapsetutorial test -n "$SYNAPSE_TUTORIAL_TOKEN" && echo "ambient token restored" All three print. Leaving the directory unloads exactly the variables the hook loaded — and if one of them had a value before activation, leaving restores that original value rather than unsetting it. The hook tracks which keys it owns precisely so it can put things back as it found them. The entire shell is trusted now Every process launched from this shell can read the value, including ones you did not think about. Prefer synapse run for anything sensitive, and keep shell tracing off while the hook evaluates — a traced shell prints what it is setting. Break the approval and watch everything refuse Approval is bound to the exact bytes of the file. A blank line is a different file: shell Copy echo "" >> .synapse.yaml synapse status . text Copy Folder: . Available: none Ambient: blocked /Users/example/tmp/synapsetutorial/.synapse.yaml [project · pending] warning: /Users/example/tmp/synapsetutorial/.synapse.yaml: Scope has not been approved Three things happen at once. Ambient mode unloads at the next prompt. synapse run refuses before launching anything. And synapse launch refuses to start a coding tool at all: shell Copy synapse run -- sh -c 'echo should-not-run' text Copy Error: vault scope is not ready: /Users/example/tmp/synapsetutorial/.synapse.yaml: Scope has not been approved It refuses rather than running with the variable missing, because a command that runs against a half-resolved environment fails in ways that look like success. Read what changed, then re-approve: shell Copy synapse scope status . synapse allow Both presence tests work again. This is what makes a committed .synapse.yaml safe: a teammate's edit, or a malicious one, arrives untrusted and stays that way until a human looks at it. Prove that deny wins Scopes resolve from the root of your filesystem down to the current folder, so a narrower file can add variables. It can never add back one a broader scope denied. Move the variable into deny : yaml Copy version: 1 scope: project env: {} deny: - SYNAPSE_TUTORIAL_TOKEN shell Copy synapse allow synapse status . Available no longer lists the name. Create a folder-level scope beneath this one that tries to map it again, approve that too, and it still will not appear. Deny is a ceiling, not a default. See why global is not the same as ambient A secret can be marked global, meaning it is available without any scope file naming it: shell Copy synapse secret global tutorial.demo on synapse secret list tutorial text Copy tutorial.demo is now global text Copy tutorial.demo SYNAPSE_TUTORIAL_TOKEN global Now move to a folder with no .synapse.yaml at all and check: shell Copy cd ~ && synapse status . text Copy Folder: . Available: SYNAPSE_TUTORIAL_TOKEN Ambient: inactive Read those two lines together, because the difference between them is a deliberate safety property. The name is available — synapse run here would provide it. But ambient is inactive : a shell hook will not load it, because an ambient environment never activates from a global mapping alone. Turning on a global secret cannot silently populate every shell you open. shell Copy synapse secret global tutorial.demo off Check what a connected tool can actually see Go back to the tutorial folder and ask a connected coding tool to call vaultstatus . It reports the variable names available here, the trust state of each scope, and whether ambient mode is ready — and no values, because that tool has no code path that reads one. The same is true of a preview. synapse launch claude --print shows DEMO_TOKEN=<from the vault> rather than a redacted value, because a preview calls a different function that lists names and never opens the vault at all. There is no value in the process to leak. Clean up shell Copy synapse deny synapse secret forget tutorial.demo synapse vault delete tutorial cd .. && rm -rf synapsetutorial text Copy Forgot tutorial.demo Deleted vault tutorial The next prompt unloads the ambient value. Forgetting the secret removes the stored value and Synapse's record of it, from both stores rather than only the one in use — so a machine that has switched backends leaves nothing behind in the one it stopped reading. What you can rely on Secret values never reach brain.db , YAML, an MCP response, or a log, and are never at rest in plaintext. Secrets are never accepted as command arguments — only from a TTY prompt or stdin. An ambient shell never activates from a global mapping alone, nor from an unapproved, changed, or incomplete scope. Unloading restores values that existed before activation rather than unsetting them. Any scope warning refuses run and launch outright rather than proceeding with part of the environment. Next step Continue with Keep one skill library across every tool to finish the daily driver level."},{"title":"Keep one skill library across every tool","description":"Write an Agent Skill once, install it into every connected tool together, and watch Synapse tell the difference between a library that moved on and a copy somebody edited by hand.","path":"tutorials/skills/","text":"Outcome and prerequisites You will write one Agent Skill, install it into every connected tool from a single source, and then deliberately create both kinds of drift so you can see how Synapse tells them apart. By the end you will know exactly when Synapse will overwrite a file and when it will refuse. The synapse CLI installed and on PATH . Codex, Claude Code, or both installed. They do not need to be connected for this tutorial — skills are copied into each tool's own folder, not through MCP. About fifteen minutes. Nothing here touches your memory store. If you have not connected a tool yet, start with Install and connect your first tools . See what is already there shell Copy synapse skill list Synapse ships one skill, so a fresh library is not empty: text Copy synapse-mesh 1 Run a team of coding agents on the Synapse mesh. Use when a job is large enough to split a… The columns are the skill name, how many tools have it installed, and the start of its description. That description is the part an agent reads to decide whether the skill is relevant, so it matters more than the body: a skill with a vague description never gets loaded. Why one library at all Claude Code reads personal skills from ~/.claude/skills and Codex reads them from ~/.agents/skills . A skill you want in both gets copied twice, and the two copies start drifting the first time you improve one of them. Synapse keeps the original and installs from it. Write a skill shell Copy synapse skill create release-checklist The command reports where it landed and what to do next: text Copy Created ~/Library/Application Support/synapse/skills/release-checklist/SKILL.md Edit it, then run `synapse skill install release-checklist`. Open it with synapse skill edit release-checklist , which uses $EDITOR , or edit the file directly. Replace the template with something real: markdown Copy --- name: release-checklist description: Walk the release checklist for this project. Use when cutting a release, tagging a version, or when the user asks what still needs doing before shipping. --- ## Before you tag 1. Run the full test suite and paste the failure count, not a summary. 2. Confirm the version in the manifest matches the tag you are about to push. 3. Check that generated output was rebuilt from source rather than hand-edited. ## After the build Report the published artifact and where it can be downloaded. If notarization was skipped, say so explicitly rather than reporting success. Two rules the frontmatter has to follow: name must match the directory name, and a description containing a colon followed by a space has to be quoted or YAML reads it as a nested key. Synapse reports either problem by name rather than passing the raw parser error through. A skill is a directory, not a file scripts/ , references/ , and assets/ beside the SKILL.md travel with it and are copied whole. Agents load only the name and description until a task calls for the skill, so a long reference file costs nothing until it is actually needed. Install it into every tool at once Check where things stand first. Every skill is listed against every detected tool: shell Copy synapse skill status text Copy release-checklist Codex not installed synapse-mesh Codex not installed release-checklist Claude Code not installed synapse-mesh Claude Code not installed Now install everything, into everything: shell Copy synapse skill install text Copy release-checklist → Codex synapse-mesh → Codex release-checklist → Claude Code synapse-mesh → Claude Code Run synapse skill status again and all four rows read installed . Confirm with your own eyes that two real copies exist: shell Copy ls ~/.claude/skills/release-checklist/ ls ~/.agents/skills/release-checklist/ You can narrow either axis. synapse skill install release-checklist does one skill into every tool; synapse skill install --tool claude does every skill into one tool. Codex does not read <code>~/.codex/skills</code> for this That folder holds the set Codex ships with. Personal skills live in the shared ~/.agents/skills location, and that is the only place Synapse writes. Let the library move on This is the ordinary case: you improve the skill and the installed copies fall behind. Add a line to the library copy: shell Copy synapse skill edit release-checklist # add anything to the body synapse skill status text Copy release-checklist Codex update available synapse-mesh Codex installed release-checklist Claude Code update available synapse-mesh Claude Code installed update available means Synapse installed this copy and the library has changed since. Installing again brings them back into step, with no prompt and no flag: shell Copy synapse skill install release-checklist synapse skill status All rows read installed again. Nothing was at risk here, because the copy being overwritten was one Synapse wrote and had not been touched since. Now edit an installed copy by hand This is the case that matters. Edit the copy inside Claude Code's folder rather than the library: shell Copy echo "" >> ~/.claude/skills/release-checklist/SKILL.md echo "## A note I added directly in the tool" >> ~/.claude/skills/release-checklist/SKILL.md synapse skill status text Copy release-checklist Codex installed release-checklist Claude Code changed in place Ask Synapse to install over it and it refuses, naming the skill and the tool: shell Copy synapse skill install release-checklist --tool claude text Copy warning: release-checklist → Claude Code: `release-checklist` in Claude Code was changed in place — pass the replace option to overwrite it Error: 1 skill install(s) did not happen That refusal is the whole point of the feature. Synapse records the digest it wrote and the library digest it came from; without that record, \"the library moved on\" and \"somebody wrote this by hand\" look identical, and the only safe response to both would be to do nothing. With it, the first case is silent and the second stops and asks. When you have decided the local edit is expendable: shell Copy synapse skill install release-checklist --tool claude --replace The refusal is per skill, not per run A run that installs ten skills and hits one changed copy installs the other nine and reports the one it did not. The exit status is non-zero so a script notices, but nothing is left half-applied. Adopt a skill you already wrote A skill Synapse did not install is never written over and never deleted — but it is also never kept in step, because Synapse has no claim on it. Adopting one changes that. It copies the skill into the library and records the tool it came from as already having it, so the original stops reading as somebody else's: shell Copy synapse skill status # look for "not in the library" synapse skill adopt my-existing-skill --tool claude synapse skill list # it is yours now synapse skill install my-existing-skill # and now it reaches the other tool too Adopting copies. It does not move or delete the original, so a mistake here costs you nothing. Clean up shell Copy synapse skill remove release-checklist # take it out of every tool synapse skill delete release-checklist --confirm # and out of the library The order matters. Deleting from the library does not remove copies already installed — Synapse would then be deleting files it can no longer prove it wrote. Remove first, delete second, and nothing is left behind. Leave synapse-mesh where it is if you plan to continue; the mesh tutorial uses it. What you can rely on A skill Synapse did not install is never overwritten or deleted, in either direction. Only personal skills are managed. A repository's own .claude/skills or .agents/skills belongs to that repository and is left alone. A SKILL.md that does not parse is reported and skipped rather than copied anywhere in a broken state. synapse disconnect and synapse uninstall take back only skills with an install record. Everything you wrote survives both. Next step Continue to Start a tool with everything in place , which is the first tutorial in the team operator track, or go back to the tutorial index to pick a different level."},{"title":"Let your agents write skills","description":"Turn on self-improvement, watch a session write down a procedure it worked out, approve it deliberately, then correct it and put the correction back.","path":"tutorials/learn/","text":"Outcome and prerequisites You will let a session write a skill, prove for yourself that it reaches no tool until you say so, approve it, correct it, and undo the correction. By the end you will know exactly what an agent can and cannot do to your skill library. The synapse CLI installed and on PATH . Claude Code, Codex, or pi connected. Install and connect your first tools if not. About twenty minutes. Nothing here touches your memory store. Read Keep one skill library across every tool first if the library itself is new to you — this tutorial assumes you know what installing a skill does. Turn it on shell Copy synapse settings learn on synapse settings show | grep learn Two tools, teach and revise , appear in every connected session the next time it starts, along with the guidance explaining them. Both arrive and leave together — a tool a session cannot explain is one it will use wrongly. Why it is off by default Two more tool definitions cost context in every session that loads them, whether or not they are used. Somebody who does not want agents editing a library should never pay for the option. Let a session teach you something Start a connected tool and give it a job with a procedure buried in it — something with steps you would otherwise re-derive. A release, a tricky deploy, a debugging path that took three wrong turns. When it finishes, ask it plainly: text Copy That took a while to work out. Write it down as a skill so the next session starts from it. It calls teach with a name, a one-line description, and the steps as it would give them to somebody doing this for the first time. Synapse writes the frontmatter itself — a model never gets to invent YAML keys or a name that disagrees with its own directory. You can also just let it happen. A session about to be compacted is asked to write down both what it learned and what it worked out, which is the moment where not having done so costs immediately. Read the queue shell Copy synapse skill proposed One row per skill nobody has looked at, oldest first, with the tool that wrote it and the line it left saying why. Read the thing itself before deciding: shell Copy synapse skill show cut-a-release Start a new session in the same project and you will be told the count once, at the boundary, and never again — Synapse connected · 1 skill to review . Waiting is what a proposal is for; a queue that interrupts is not a queue. Prove the gate for yourself Do not take it on faith. Look where the skill would be if it had been installed: shell Copy ls ~/.claude/skills/ synapse skill status cut-a-release It is not there, and the status says waiting for review rather than not installed — a different fact with a different fix. Now try the blunt instrument: shell Copy synapse skill install ls ~/.claude/skills/ Still not there. Installing everything means everything approved ; a proposal reaching a tool because somebody ran a bulk install is the one way this gate could leak, so both the CLI and the app filter it explicitly. Why the gate is on installing, not writing The library is Synapse's own folder, so writing there costs nobody anything. A skill's description is loaded into every session of every tool holding it — that is the bill you have to agree to. So teaching is free and installing is the decision, rather than the other way around. Approve one shell Copy synapse skill approve cut-a-release synapse skill status cut-a-release Now it is installed, and the queue is empty. If the agent scoped it to the project, it went into that repository's own skills folder rather than your home — a procedure about one checkout belongs to it. Turning one down removes it and its history: shell Copy synapse skill reject some-other-skill --confirm Only something still waiting can be rejected. Once approved it is an ordinary skill, and skill delete is what removes it — a command that says what it does. Let it correct itself Use the skill in a session and let it turn out wrong — a missing step, a stale path. Tell the session: text Copy Step 3 is wrong now. Fix the skill. It calls revise , and this one does reach the copies Synapse installed: shell Copy grep -c "the corrected step" ~/.claude/skills/cut-a-release/SKILL.md That is the deliberate exception. You already agreed to this skill being loaded, and a correction that never arrives leaves every session running the version that was wrong. It reaches only copies Synapse wrote and nobody has edited — one you changed by hand is yours, and is left where it is. Take a correction back shell Copy synapse skill history cut-a-release synapse skill revert cut-a-release The history is every version the skill has had, newest first, with the line saying what was wrong with each. Reverting puts one back in the library and in every tool holding a Synapse copy — and is itself recorded as a revision, so a revert can be reverted. The newest twenty are kept. This is the same bargain a corrected memory makes: nothing is hidden without a way back. Clean up shell Copy synapse skill remove cut-a-release synapse skill delete cut-a-release --confirm synapse settings learn off Turning it off leaves everything already in the library exactly where it is. What changes is that no new session gets the tools. What to read next Curate and optimize memory is the same idea for the other half of what a session learns — facts rather than procedures. Hand a job to one agent gives you a session long enough to work out a procedure worth keeping."},{"title":"Start a tool with everything in place","description":"Use synapse launch to open Codex, Claude Code, or pi with memory, scoped credentials, and the project root already wired, without writing anything into that tool's own configuration.","path":"tutorials/launch/","text":"Outcome and prerequisites You will open a coding tool that has memory, the folder's scoped credentials, and the right project root from its first turn — and confirm that your machine is exactly as it was afterwards. synapse launch wires a tool for the life of one process. It is not a setup step and it writes nothing into the tool's own configuration. The synapse CLI installed and on PATH . Codex, Claude Code, or pi installed. None of them needs to be connected — that is the point of this tutorial. A scratch project folder. Steps four and five create a disposable vault value. Launching is not connecting synapse connect is a decision you make once: it registers the MCP server in the tool's own configuration and leaves it there. synapse launch makes no such change. If the tool has no connection of its own, Synapse hands it a generated configuration for that one process and the machine is unchanged when it exits. Preview before you launch Work in a scratch folder so nothing here touches a real project: shell Copy mkdir -p "$HOME/tmp/launchtutorial" && cd "$HOME/tmp/launchtutorial" git init --quiet . synapse launch claude --print --print resolves everything and shows you the result instead of running it: text Copy /Users/example/.local/bin/claude --mcp-config ~/Library/Application Support/synapse/relay/launch.66c4a25fcfe8.mcp.json env SYNAPSE_PROJECT_DIR=/Users/example/tmp/launchtutorial Try Codex too. It reads no MCP configuration file, so it gets the server on its command line instead: shell Copy synapse launch codex --print text Copy /Users/example/.asdf/shims/codex -c mcp_servers.synapse.command="/Users/example/.local/bin/synapse" -c mcp_servers.synapse.args=["mcp"] env SYNAPSE_PROJECT_DIR=/Users/example/tmp/launchtutorial Read what it actually did Three things were resolved, and each is worth understanding before you trust the command with a real project. Piece Where it came from The program Looked up in the registry of connectable tools, then on PATH . A tool that is not installed fails here with a name, not a shell error. The MCP wiring Written only because this tool has no Synapse connection of its own. A connected tool is launched as-is and gets no generated file at all. SYNAPSE_PROJECT_DIR The folder you launched from, walked up to its project root. This is what scopes the tool's memory and, on the mesh, its registration. The generated configuration is keyed on a digest of the project root, so relaunching in the same folder reuses one file rather than accumulating them. Look at it if you like — it names this binary and the mcp argument, and nothing else. Why this exists at all Before launch , wiring a tool meant either connecting it permanently or assembling the arguments by hand. The first is a commitment you might not want on a machine you are borrowing; the second is easy to get subtly wrong. launch works before setup has ever run and leaves nothing behind. Add scoped credentials A launched tool can run a shell, so it gets the same scoped environment synapse run would give a child. Create a disposable value and map it to this folder: shell Copy synapse vault create demo synapse secret set demo token DEMO_TOKEN Enter a throwaway value at the hidden prompt. Then create the scope and approve it: shell Copy synapse scope init . yaml Copy version: 1 scope: project env: DEMO_TOKEN: demo.token deny: [] shell Copy synapse allow synapse launch claude --print The preview now names the variable and refuses to show what is in it: text Copy /Users/example/.local/bin/claude --mcp-config …/launch.66c4a25fcfe8.mcp.json env SYNAPSE_PROJECT_DIR=/Users/example/tmp/launchtutorial env DEMO_TOKEN=<from the vault> <from the vault> is not a redaction applied to a value that was read. A preview calls a different code path that lists names and never opens the vault at all, so there is no value in the process to leak. Watch it refuse a half-resolved environment Break the scope's approval by editing the file — a blank line is enough, because approval is bound to the exact bytes: shell Copy echo "" >> .synapse.yaml synapse status . text Copy Folder: . Available: none Ambient: blocked /Users/example/tmp/launchtutorial/.synapse.yaml [project · pending] warning: /Users/example/tmp/launchtutorial/.synapse.yaml: Scope has not been approved Now try to launch: shell Copy synapse launch claude --print text Copy Error: vault scope is not ready: /Users/example/tmp/launchtutorial/.synapse.yaml: Scope has not been approved It refuses rather than starting the tool with DEMO_TOKEN missing. That is deliberate and it is the same rule synapse run applies: a tool that can run a shell is never handed a partly-resolved environment, because the failure mode is a command that runs against the wrong thing and looks like it worked. Read the changed file, then approve it again: shell Copy synapse scope status . synapse allow synapse launch claude --print Pass the tool its own flags Everything after a bare -- reaches the tool untouched: shell Copy synapse launch claude --print -- --resume --model opus text Copy /Users/example/.local/bin/claude --mcp-config …/launch.66c4a25fcfe8.mcp.json --resume --model opus env SYNAPSE_PROJECT_DIR=/Users/example/tmp/launchtutorial env DEMO_TOKEN=<from the vault> The split happens before Synapse parses anything, which is why a flag both programs understand still reaches the right one. Without that, a --model meant for the tool would be eaten by Synapse. Launch for real shell Copy synapse launch claude The tool opens as it normally would. Ask it what Synapse tools it has; the answer should include remember , recall , readmemory , and vaultstatus . Ask it to call vaultstatus and it will report DEMO_TOKEN as available — the name only, because that tool returns metadata and cannot read the value. If this is Claude Code and it is also connected, its first message will carry this project's memory; see Session start . A launched tool that is not connected has the MCP tools but not the session hook, because the hook lives in that tool's own settings and launching writes nothing there. Exit the tool. Then confirm the machine is unchanged: shell Copy synapse status The tool's own configuration was never opened. Nothing needs undoing. Clean up shell Copy synapse deny synapse secret forget demo.token synapse vault delete demo cd .. && rm -rf launchtutorial Forgetting the secret removes the stored value and Synapse's record of it. The generated MCP configuration under the data folder is harmless — it names this binary and nothing else — and is overwritten on the next launch in the same folder. What you can rely on Launching never edits the tool's own configuration. Making a connection permanent is synapse connect , a separate decision. A preview prints variable names and never a value, because it never reads one. Any scope warning refuses the launch outright rather than starting the tool with part of its environment. A connected tool is launched as-is. The generated configuration exists only for a tool that has no connection of its own. Next step Continue to Run a team of agents and drive it yourself , which uses the same launch pipeline to open several tools at once and put you on the roster with them."},{"title":"Run a team of agents and drive it yourself","description":"Turn on the agent mesh, open a team, watch what every agent is doing from one terminal, answer a worker that gets stuck, and shut the whole thing down cleanly.","path":"tutorials/mesh/","text":"Outcome and prerequisites You will put several coding-agent sessions on one local mesh, give them work from your own seat rather than through a lead agent, read what each one is doing without opening its terminal, and answer one that gets stuck. Then you will take it all down and confirm nothing is left running. The synapse CLI installed, and at least one tool connected. Start with Install and connect your first tools if not. A real project folder. Agents work on real files, so use a checkout you can throw away or one with clean version control. Thirty to sixty minutes, and a willingness to let several agent sessions run at once. These are real agents on your real machine A background worker runs with its own tool's permission prompts bypassed, because there is no terminal in which to answer one. Everything below happens on your actual files. Use a repository whose state you can restore. Turn the mesh on The mesh ships switched off. Its sixteen tools load into every connected session, and that costs context in each one, so Synapse does not add them to a setup that will not use them. shell Copy synapse relay status text Copy Mesh: off Agents: 0 online of 0 Workers: 0 Turn it on with `synapse settings mesh on`. shell Copy synapse settings mesh on text Copy Agent mesh on. Connected tools pick this up the next time they start. That last sentence is literal. A session already running keeps the tool list it started with, so restart any open tool before continuing. The same applies when you switch the mesh back off. Turning the mesh on also brings the guidance that explains it. The tools and the instructions for using them appear together and are withdrawn together, so a session can never have one without the other. Look at roles and teams A role is a durable identity: a brief describing what an agent owns and how it coordinates. A team is a named roster of roles. Both ship with sensible defaults: shell Copy synapse relay role list text Copy backend built-in devops built-in frontend built-in qa built-in reviewer built-in supervisor built-in worker built-in shell Copy synapse relay team list synapse relay team show web text Copy # web · built-in name = "web" [[member]] name = "lead" role = "supervisor" [[member]] name = "frontend" role = "frontend" [[member]] name = "backend" role = "backend" [[member]] name = "reviewer" role = "reviewer" Read one role to see what an agent is actually told: shell Copy synapse relay role show reviewer text Copy # reviewer · built-in name = "reviewer" description = """ You review other agents' work. Read the diffs they report, check correctness, edge cases, and conventions, and send concise, actionable feedback to the author. Approve only when it holds up; escalate disagreements to the supervisor. """ Roles resolve from the project first, then your own layer, then the built-ins. A role saved into a project lives in .synapse/roles/ and travels with the checkout, which is how a team convention becomes something the repository carries rather than something each person configures. Join as yourself, not through a lead There are two ways to run a team, and the difference matters. synapse relay team open web launches every member and puts a lead agent in your terminal. You brief the lead, the lead hands out tasks and relays answers back. That costs the lead's context on every message and makes it a bottleneck for work it is not doing. synapse mux puts you on the roster instead, with the same messaging every agent has: shell Copy cd ~/your-project synapse mux --team pair Synapse launches the team's members in the background and drops you into a line-oriented prompt with your own name on the roster. It is deliberately plain — no terminal library, no full-screen interface — so it works over ssh and in any terminal. text Copy @backend the created column needs a default, not a backfill #build freezing the schema in ten minutes ! stop and report where you are /focus backend and the index too Syntax Goes to @name text One agent, directly. #channel text Every subscriber of that channel. ! text Everyone on the mesh. text Whoever is focused, so a back-and-forth reads like a conversation. /help lists the rest. The ones you will use are /agents , /workers , /focus <name> , /log <name> , /kill <name> , and /quit . Watch what everyone is doing From inside the mux, /agents shows the roster. From another terminal, the same thing: shell Copy synapse relay agents text Copy you — you — — /work/api lead supervisor online working splitting the migration into three tasks /work/api backend backend online blocked need the staging database name /work/api frontend frontend online working rewriting the auth middleware /work/api The fourth column is a state and the fifth is the note that goes with it. The state tells you an agent has not stalled; the note is the part that tells you whether to leave it alone. For a headless worker there is no terminal to look at, so that note is the only view of it there is. Notes are one line, bounded, and kept when a later report does not carry one — an agent that reports working twice has not stopped doing the thing it described the first time. shell Copy synapse relay status # counts and whether the mesh is on synapse relay channels # channels and how many subscribe to each synapse relay feed --follow # every message between agents, as it happens Every one of these takes --json . The Mesh page in the desktop app shows the same information with the traffic alongside it. Notes are guidance, not a guarantee Writing one is something connected tools are told to do, not something Synapse can enforce inside another tool's session. A stale note is possible; the roster's own liveness is what tells you whether the agent is still there, and an agent that stops answering leaves the roster within about a minute and a half. Understand the wait loop This is the one piece of the mesh worth understanding properly, because almost every confusing behavior traces back to it. An idle agent calls wait , which blocks until a message arrives for it. After a few idle minutes it returns an empty list instead. That is a normal timeout, and the agent's instructions are to call wait again. An idle teammate therefore costs one tool call every few minutes rather than a loop that spins. Two consequences you will actually notice: Messages arrive at the next check, not as an interrupt. An agent parked between tasks answers in about a second. One in the middle of a long build sees you when it comes back. The roster's state column tells you which is which. An empty or failed wait is not a signal to stop. Agents are told this explicitly, in the guidance and again in the launch harness, because an agent that reads a timeout as \"the work must be finished\" writes an explanation and exits. That is the single most common way a mesh session dies. Delivery is at least once. A reply lost in flight is delivered again rather than dropped, so a duplicate is possible on that rare path and a lost message needs a process to die inside a very small window. Grow the team while it works You do not have to decide the roster up front. A supervisor can spawn workers as it discovers what the job needs, and you can do the same from the command line: shell Copy synapse relay launch migrations --role backend --task "write the schema migration" synapse relay ps synapse relay kill migrations A worker started this way runs headless, registers itself, and parks on wait until it is given something to do. From inside the mux, /workers lists them and /log <name> shows what one has actually been doing. Workers belong to the session that started them, for exactly as long as that session lives. There is no daemon: closing the mux takes its workers with it, so nothing is left running behind you. A worker that exits is restarted with a growing backoff, and one that never manages a healthy run is retired rather than restarted forever. At most eight run at once. Answer a worker that gets stuck This is the reason to be on the roster yourself rather than behind a lead. A headless worker runs with its permission prompts bypassed, so until there is a person on the mesh it has nobody to ask when it reaches a decision it should not make alone — and its only option is to guess. With you on the roster it can send you the question, report itself blocked with a note saying what it needs, and wait. text Copy backend: the migration drops a column with data in it. Confirm before I run it? /focus backend yes, it is a duplicate of created_at — go ahead Agents can tell a person from an agent. Your roster row is marked as a human, and connected tools are told to ask you questions and never delegate work to you. Only synapse mux can set that flag; a tool calling register is always an agent, whoever is sitting in front of it. Write a role of your own The built-ins are a starting point. Create one in the project so it travels with the checkout: shell Copy synapse relay role create migrator toml Copy channels = ["build"] tool = "claude" # model = "claude-opus-5" # driver = true # stay interactive instead of parking # tools = ["Read", "Edit", "Bash(git:*)"] # pre-granted tool rules description = """ You own database migrations. Write them forward-only, never destructive without asking, and post the SQL to #build for review before running anything. """ Editing a built-in copies it down into a layer you own rather than modifying the shipped template. --user writes into your own layer instead of the project. Then build a team around it: shell Copy synapse relay team create schemawork synapse mux --team schemawork Shut it down shell Copy # from inside the mux /quit # then, from any terminal synapse relay ps # should be empty synapse relay agents # names age off within about ninety seconds synapse settings mesh off Leaving the mux takes its workers with it and takes your name off the roster, so nothing addresses an empty terminal. Switching the mesh off removes the sixteen tools and their guidance from every session that starts afterwards; sessions already running keep what they had until they restart. Messages stay in the local database and remain visible through synapse relay feed . They are memory-adjacent, not memory: they are never recalled and never returned by recall . What you can rely on No daemon, no port, no token. The bus is the same local database as your memory, and a message between two agents never leaves your Mac. A message from another agent is information, not instruction. Connected tools are told to treat mesh traffic as untrusted input that never overrides you, your shared guidance, or repository rules. A person on the mesh is addressed, never assigned. Only the mux can mark a roster row as human. Channel and broadcast history is not replayed — a new agent sees only what is sent after it joins, so start workers before briefing them. A direct message is held for an agent that has not registered yet. Workers die with the session that started them. A session that closes takes its name off the roster with it. Next step You have finished the team operator track. The maintainer track covers keeping the store healthy and leaving cleanly: Export and restore safely , then Check, migrate, and remove Synapse ."},{"title":"Hand a job to one agent and watch it grow a team","description":"Start a single overseer, describe an outcome instead of picking a roster, answer it when it asks, and see why nothing sits between you and the workers it starts.","path":"tutorials/overseer/","text":"Outcome and prerequisites You will start one agent, give it a goal rather than a task list, and let it decide whether the work needs a team at all. Along the way you will answer a question it cannot answer itself, and message a worker it started without going through it — which is the difference between this and every other way of running agents. The synapse CLI installed and on PATH , with the mesh on: synapse settings mesh on . Claude Code installed and signed in. The agents run under whatever account you already have — Synapse holds no model credential and makes no model call. A checkout you can afford to have edited. Agents here touch real files. About thirty minutes. If the mesh is new to you, read Run a team of agents and drive it yourself first. This tutorial is the same machinery with one agent instead of four. Start one agent, not a roster The other teams ask you to choose a shape before you understand the job. overseer is one member: shell Copy cd ~/code/your-project synapse relay team show overseer synapse mux --team overseer Two things happened. An agent started in the background under the overseer role, and you joined the mesh under your login name. Check both: shell Copy /agents You should see two rows: yourself, marked as a person, and overseer . The mark matters — every agent on the mesh is told to ask a human row questions and never delegate work to one. Describe an outcome Say what you want to be true, not which steps to run: text Copy @overseer the release notes for the next version are missing. Work out what changed since the last tag and write them. Watch what it does before it does anything: shell Copy /agents Its note is the interesting part. An overseer's brief tells it to weigh doing the work itself against splitting it, because every worker it starts is a separate session on the same account you are paying for. A small job stays one agent. A job with two genuinely parallel halves gets a second. Spawning is not free, and it knows that The role says so in as many words: spawn for work two agents can do at the same time, not to look busy, and stop one whose piece is finished rather than leaving it idling. Answer it when it asks Sooner or later it will reach something only you can settle — which version number, whether a change is worth mentioning, whether to touch a file it is unsure about. It will send you the question and report itself blocked: shell Copy /agents A row reading blocked with a note is an agent waiting on you. Reply to it like anything else: text Copy @overseer call it 2.4.0, and leave the vendored files out entirely This is the whole reason a person belongs on the roster. A headless worker runs with its permission prompts bypassed, so without somebody to address, its only option at a fork is to guess. Reach past it If the overseer started workers, they are on the roster too — and you can address them directly: shell Copy /agents /log notes text Copy @notes skip the dependency bumps, they are noise Nothing was relayed. You did not interrupt the overseer's context to correct one of its workers, and it did not have to spend a turn passing the message along. Why that matters more than it sounds A lead that relays for you pays its own context on every message and becomes a bottleneck for a job it is not doing. The overseer is addressable, never interposed — which is the same reason synapse mux exists at all. Bound the fan-out How many agents a machine can usefully carry is a fact about the machine: shell Copy synapse settings workers 4 synapse settings show | grep workers A supervisor already running picks this up on its next spawn. The number is clamped to a ceiling in code as well as refused on the way in, so a mistyped digit cannot buy an unbounded fleet — the setting is a preference, and the ceiling is the promise. Clean up shell Copy /workers /kill notes /quit Leaving takes the workers it started with it, and drops your roster row so no agent goes on addressing questions to somebody who is not there. What to read next Run a team of agents and drive it yourself is the same mesh with a roster you choose. Let your agents write skills turns a procedure one of these sessions worked out into something the next one starts with."},{"title":"Export and restore safely","description":"Create a validated snapshot, make one reversible change, stop every database user, restore, and prove the previous state came back — including what a snapshot deliberately does not carry.","path":"tutorials/recovery/","text":"Outcome and prerequisites You will take a consistent snapshot of the whole store, make a change you can see, restore over it, and prove the change is gone. Then you will understand exactly what a snapshot contains, what it deliberately leaves out, and how that affects moving to another machine. The synapse CLI installed. A destination path that does not already exist. A terminal you can keep open, to record one memory ID. This really does replace the active database The tutorial is designed to be reversible and Synapse writes a pre-restore snapshot of its own before it does anything. But restore is a genuine database replacement. Read each step before running it, and do not substitute a production backup you have not separately preserved. Check the store before you trust a copy of it shell Copy synapse path synapse data check text Copy Database: /Users/example/Library/Application Support/synapse/brain.db Version: 7 Integrity: ok Integrity must read ok . Two separate checks stand behind that word: a relationship check that runs every time the database opens, and a full page-by-page scan that reads the whole file. Exporting a store that fails either one copies the problem rather than saving you from it. If it reports anything else, stop here and follow the corruption guidance in Troubleshooting before continuing. Create a fresh export shell Copy backup="$HOME/Desktop/synapsetutorialbackup.db" test ! -e "$backup" synapse data export "$backup" text Copy Exported /Users/example/Desktop/synapsetutorialbackup.db That one line hides four steps. Synapse writes a consistent compact snapshot rather than copying a file that may be mid-write, secures its permissions to owner-only, reopens it read-only, and validates it — all before reporting success. An export that would not open is a failure here rather than a surprise months later. The snapshot is a complete SQLite database. It holds every memory with its scope and project, vault and secret metadata , scope trust records, import batches, skill install receipts, mesh history, and your settings. Add one marker after the export shell Copy printf '%s\\n' 'This entry exists only after the tutorial export.' \\ | synapse memory add synapserecovery synapse memory list synapserecovery text Copy Stored memory #42 Record that ID. It is the marker that must disappear when the restore succeeds — which is what turns this from \"the command did not error\" into an actual proof. Stop every database user Quit the Synapse desktop app. Close every connected session so its synapse mcp child process exits. A plain CLI command releases its lock when it finishes, so nothing needs doing about those. Reading the database takes a shared lock; restoring requires an exclusive one. If you deliberately leave a session open, the next step will refuse and tell you to close the app and connected tools. Do not work around the lock The refusal is not a formality. Replacing a database file underneath a process that has it open is how a store gets corrupted in a way no integrity check can undo. If restore refuses, find the process that is still holding it. Restore shell Copy synapse data restore "$backup" text Copy Restored /Users/example/Desktop/synapsetutorialbackup.db Previous database: /Users/example/Library/Application Support/synapse/backups/brain.1785776950101.restore.db Read the second line. Before replacing anything, Synapse wrote the current database into the backups folder with a restore marker in its name. If you have just restored the wrong file, that path is your way back — and it is printed rather than left for you to discover. The sequence is: validate the source, validate the current database, acquire the exclusive lock, snapshot the current state, then replace atomically. Any step failing leaves the original in place. Verify the earlier state actually returned shell Copy synapse data check synapse memory list synapserecovery Integrity should read ok , and the marker must be absent — an empty result is the success condition. Confirm something you expect to still be there as well, so you are testing that the right state returned rather than that the database is merely empty: shell Copy synapse memory list Reopen the desktop app and your connected tools only after this verification passes. What a snapshot does not carry This is the part that surprises people, and it follows directly from the security model. Not in the export Why, and what to do Secret values They live in their own store and were never in brain.db . On the same machine, restored metadata still points at them and everything works. On another machine, recreate each with synapse secret set — or, on the encrypted store, carry vault.db and vault.key across together, since neither is any use without the other. Your SOUL.md A file beside the database, not a table inside it. Copy it separately if it matters — and it usually does. Your skill library Directories under the data folder. Copy skills/ separately. Roles and teams TOML under the data folder, plus anything in a project's own .synapse/roles/ , which travels with that checkout instead. Tool configuration Belongs to the tools themselves. Reconnect on the new machine rather than copying their files. shell Copy # a complete manual copy, when the database alone is not enough cp -R ~/Library/Application\\ Support/synapse ~/Desktop/synapse-full-copy Backups you did not ask for Synapse writes snapshots on its own at two moments: before any schema migration, and before any restore. Both land in the same place: shell Copy ls ~/Library/Application\\ Support/synapse/backups/ Only the newest few are kept, so the folder cannot grow without limit — the same rule that bounds worker logs and the crash log. That means an automatic backup is a safety net for the operation that just happened, not an archive. If you want a copy to keep, take an explicit export and move it somewhere Synapse does not manage. Moving to a new Mac On the old machine Run synapse data export , and copy SOUL.md and the skills/ directory alongside it. Note which secrets exist with synapse secret list for each vault — the names, since you cannot export the values. On the new machine Install Synapse, install the CLI, then restore the export before connecting any tools. Put SOUL.md and skills/ back in the data folder. Then Recreate each secret with synapse secret set , re-approve each project scope with synapse allow — approval is per machine, because it is a statement about a file you have read — connect your tools, and run synapse skill install . shell Copy synapse doctor One command to confirm the new machine matches the old one: same schema version, same memory count, tools connected, skills installed. Next step Finish the maintainer level with Check, migrate, and remove Synapse , which covers the health report in full and both removal paths."},{"title":"Check, migrate, and remove Synapse","description":"Read a full health report, understand what a schema migration does to your store, disconnect one tool, and remove everything Synapse installed while keeping your memory and your own files.","path":"tutorials/lifecycle/","text":"Outcome and prerequisites You will produce the report to attach to a bug, verify the store's integrity and schema version, understand what happens when a release changes the schema, and then walk both removal paths — one tool, and everything — reading what each will do before it does it. The synapse CLI installed and at least one tool connected. Export and restore safely read first. This tutorial assumes you know how to take a snapshot; the removal steps are more comfortable when you have one. Read before you confirm Every destructive command here refuses to act without --confirm and prints what it would do instead. That preview is the feature. Run each command once without the flag and actually read the output before adding it. Get the whole picture in one command shell Copy synapse doctor text Copy Synapse 0.1.0-beta.24 Store State ok Schema v10 Memories 0 Size 0 MB Backups 0 Recall budget balanced Connected tools Claude Code installed, not connected version 2.1.241 (Claude Code) guidance no notice no compaction no status line none Codex installed, not connected version codex-cli 0.147.0 guidance no notice no compaction no status line none pi not installed Skills In the library 1 Installed 0 Out of date 0 Agents write no This is what to attach to a bug report. It answers most first questions without a round trip: which version, whether the store is sound, what schema it is on, whether each tool is actually connected rather than merely installed, and whether the guidance pointer, session notice, and status line are in place. --json gives the same thing structured. Note what is not here: no memory contents, no secret names, no file paths outside Synapse's own. A doctor report is safe to paste into a public issue. Read the tool rows carefully. installed, not connected means the tool is on the machine but has no Synapse entry. Agents write under Skills is whether self-improvement is on, and a count of anything waiting for you appears under it when there is any. A tool that was connected and whose executable later moved reports as stale rather than healthy, because detection resolves the stored command rather than trusting the entry exists. Check the store itself shell Copy synapse data check synapse data check --json text Copy { "path": "~/Library/Application Support/synapse/brain.db", "version": 7, "integrity": "ok" } Two different checks run when the database opens, with very different costs. A relationship check is index-driven and runs every single time. A full page-by-page scan reads the whole file, so it runs once per process against an unchanged file — which is why a status line redrawing on every turn stays cheap while data check is thorough. integrity: ok means both passed. Anything else is reported rather than worked around, and restoring a snapshot is the answer. Understand what a migration does Schema v7 in the report above is the store's version. Each release that changes the schema adds a numbered migration and raises that number. Opening the database applies any that are missing, in order. Three things are true of every migration and worth knowing before you upgrade: A backup is taken first. Before any migration runs against an existing store, Synapse copies it into the backups folder. If a migration fails, that copy is what you restore. Shipped migrations are never edited. A release only ever appends. That is what makes the upgrade path from any older version deterministic rather than dependent on which releases you happened to install. A newer store refuses an older binary. If the database reports a version this release does not know about, Synapse says so and stops rather than guessing at a schema from the future. shell Copy ls ~/Library/Application\\ Support/synapse/backups/ Backups are bounded — only the newest few are kept — so the folder cannot grow without limit. The same rule applies everywhere Synapse writes something unbounded: worker logs and the crash log keep their tail rather than the whole history. Upgrading is just opening it There is no migrate command and nothing to run by hand. Install a new release, run any synapse command or open the app, and the store is brought forward with a backup beside it. synapse doctor afterwards will show the new schema number. Disconnect one tool Disconnecting undoes one tool's integration and nothing else: shell Copy synapse disconnect claude text Copy Removed Claude Code skill `synapse-mesh` warning: Claude Code skill `release-checklist`: `release-checklist` in Claude Code has been changed since Synapse installed it Read that second line closely, because it is the whole design in one sentence. Synapse removed the skill it had written and left untouched. It refused to remove the one that had been edited since — because at that point the file is partly yours, and Synapse will not delete work it cannot prove is its own. The same run also asks the tool's own CLI to forget the MCP server, strips the managed block from its instruction file while leaving your text in place, and removes the session hook and status line from its settings — but only if they are the ones Synapse wrote. A status line you configured yourself is reported and left alone. Every step reports rather than aborts. A disconnect that stopped at the first problem would leave the tool half-connected, which is the worst available outcome; finishing and telling you what it could not do is better. shell Copy synapse disconnect With no tool named, it disconnects every one. Read the uninstall preview Run it without --confirm first. It always previews: shell Copy synapse uninstall text Copy `synapse uninstall --confirm` would remove: · every skill Synapse installed, leaving any you wrote Your memory in ~/Library/Application Support/synapse would be left alone. Add --confirm to go ahead. On a fully connected machine the list is longer — the MCP entries, the managed instruction blocks, the session hook, the status line, the installed CLI, the shell startup block. The last line is the one to notice: memory is never removed as a side effect. Uninstalling the software does not delete what you have taught it. Remove everything, including memory Taking the data folder requires asking for it by name, and the preview changes to say so: shell Copy synapse uninstall --data text Copy `synapse uninstall --confirm` would remove: · every skill Synapse installed, leaving any you wrote And, because --data was given, everything in ~/Library/Application Support/synapse including all of your memory. That cannot be undone. Add --confirm to go ahead. Two flags, both required, and a sentence that says it cannot be undone. If you want a copy first, synapse data export writes a consistent snapshot you can restore into a fresh install later — see Export and restore safely . shell Copy synapse data export ~/synapse-final-snapshot.db # optional, but do it synapse uninstall --data --confirm Check where your values live first On the encrypted store, vault.db and vault.key are in the data folder, so --data deletes every secret value with it. The command says so before it acts. On the Keychain store the values are outside the folder and survive: removing it costs Synapse the ability to find them, so use synapse secret forget for those first. synapse vault backend says which store this machine uses. What survives The principle behind all of this: everything Synapse writes outside its own folder, it can take back — and nothing else. After a full uninstall, these are untouched: Thing Why it survives Your own words in CLAUDE.md or AGENTS.md Only the managed block between markers is removed. Text outside it was never Synapse's. A skill you wrote or edited Removal requires an install record proving Synapse wrote that exact content. A status line somebody else configured JSON has no comments, so entries are recognized by the command they run. One that is not Synapse's is reported, never replaced. Other MCP servers in either tool Removal goes through the tool's own CLI, by name, for the synapse entry only. Your memory, unless --data Memory is never removed as a side effect of removing software. Secret values on the Keychain store They were never in the data folder, so removing it cannot delete them. On the encrypted store they are in that folder, and --data takes them with it. A project's .synapse.yaml and .synapse/roles/ They belong to the checkout, not to your machine. Next step That is the whole maintainer track. If something in it did not behave as described, synapse doctor --json plus the exact command you ran is everything a bug report needs. See Troubleshooting for the common failures and what each one means."}] \ No newline at end of file +[{"title":"Start here","description":"Understand the Synapse model, choose a path through the guide, and know where its guarantees stop.","path":"docs/","text":"The model Synapse is a local service with two deliberately separate jobs. Its memory layer gives connected developer tools one durable, searchable context store. Its vault layer gives commands or opted-in shells carefully scoped environment variables while keeping secret values sealed in a store of their own. The desktop app is the control surface. The CLI exposes the same memory, vault, scope, settings, and data-lifecycle operations. The MCP stdio server gives connected tools three narrow capabilities: remember durable context, recall it, and inspect value-free vault status. An optional agent mesh adds a fourth job on top of the same store: letting those connected tools coordinate with each other. What Synapse stores Material Location Visible to Memory text, source, global or project scope, origin, and import history Local SQLite database Desktop app, CLI, and scoped MCP memory tools Shared working guidance SOUL.md in the Synapse data directory You and every connected tool through managed global pointers Vault names, secret labels, account references, global mappings brain.db , the local SQLite database Desktop app and CLI; MCP receives names only Secret values vault.db , sealed under the key in vault.key — or macOS Keychain, if that is what the machine is set to. Never brain.db . Synapse, a child launched with synapse run , or processes launched from an activated shell Project and folder mappings Approved .synapse.yaml files You, Synapse, and the repository if you commit the file Scope approvals Local SQLite database as a path and content digest Synapse Mesh roster, channels, and messages between agents Local SQLite database, while the mesh is on Desktop app, CLI, and agents that have joined Agent Skills library The Synapse data directory, copied into each tool's own skills folder You, and every tool that reads the Agent Skills format Skills agents proposed, and what a skill used to say Local SQLite database, while self-improvement is on You. A proposed skill reaches no tool until you approve it Agent roles and team rosters TOML in the project's .synapse folder or the Synapse data directory You, Synapse, and the repository if you commit them What Synapse does not do It does not sync memory or secrets to an account or hosted service. It does not send secret values through MCP, write them to YAML, or accept them as command arguments. It cannot directly modify an already-running parent process. Use synapse run -- <command> for one child, or explicitly evaluate synapse hook so your shell applies quoted environment changes itself. It does not decide that every conversation detail deserves permanent memory. Connected tools receive instructions to keep stable, confirmed context. It does not continuously mirror native tool stores. Imports are explicit, previewed, and reversible; new shared memory belongs in Synapse. Synapse provides a signed Apple-silicon desktop app for macOS 13 or later, plus a Linux CLI and MCP server. See Linux setup to build from source. Choose a path I want to use Synapse now. Follow Install and connect your first tools . It covers download, first launch, the CLI, supported harnesses, and a working recall check. I use a different coding harness. See supported and custom harnesses . Create a .synapse/tools/mytool.toml descriptor, connect it, and contribute it through a PR. I want to manage Synapse from the app. Use the desktop app reference for every screen, status, confirmation, editor, and shell-integration control. I want to understand memory behavior. Read Memory and recall , then follow Carry one decision between tools . I need credentials for local commands. Read Vaults and scopes , then build a complete approved scope in Use a scoped secret in either shell mode . I want my tools to work together. Read Agent mesh . It covers turning it on, roles, teams, background workers, and what the mesh deliberately does not do. I want to talk to my agents rather than watch them. synapse mux puts you on the mesh under your own name. Hand a job to one agent walks through describing an outcome and letting it decide whether the work needs a team. I keep re-deriving the same procedure. Let a session write it down. Let your agents write skills covers what an agent may and may not do to your library, and why the gate is on installing rather than on writing. I keep copying the same skill into every tool. Read Skills . One library, installed into Claude Code, Codex, pi, and Ainz together, with drift reported rather than silently resolved. I configure the same MCP server in every tool, token and all. Read MCP servers . One list, given to every tool through its own mcp add , with each token in the vault instead of in plaintext in three config files. I need automation or exact syntax. Use the complete CLI reference and MCP tool reference . I am planning backups or a recovery. Read Data lifecycle before exporting or restoring the database."},{"title":"Install and connect","description":"Install Synapse on macOS or Windows, or build the Linux CLI, then connect your coding tools.","path":"docs/install/","text":"Requirements macOS desktop: Apple-silicon Mac running macOS 13 or later. Windows desktop: a Windows x64 machine with Direct3D 11. Linux CLI and MCP server: a current stable Rust toolchain and a C compiler/linker to build from source. At least one supported tool on PATH : Codex, Claude Code, pi, or Ainz. A writable ~/.local/bin , or a custom path supplied through SYNAPSE_BIN . Install on Linux Linux supports the CLI and MCP server through synapsecore . This build does not depend on the desktop UI. Release downloads contain the macOS app and Windows installer; build the Linux version from the source repository . From the repository root, run: shell Copy cargo build --release --locked --manifest-path crates/synapsecore/Cargo.toml ./crates/synapsecore/target/release/synapse-cli install export PATH="$HOME/.local/bin:$PATH" synapse connect synapse status The install command copies the binary to ~/.local/bin/synapse . Keep that directory on your shell's PATH . synapse connect connects tools installed on this machine; restart them to load the connection. Use synapse status for text status or synapse mcp to run the MCP server. On Linux, credentials use Synapse's encrypted vault. The Keychain backend and the native desktop app are macOS-specific. Continue with Verify the connection ; the app and app-based setup steps below apply to macOS. Install the macOS app Download synapse.zip from the latest release . Extract the archive and move synapse.app into Applications . Do not install the CLI while the app is still inside Downloads or a mounted disk image. Open Synapse. The app is Developer ID signed and notarized by Apple, so it opens under default Gatekeeper settings with no security override. Why move it first? The installed CLI is a small launcher that points into the signed application bundle. Moving the app later would leave that launcher pointing at the old location. Install on Windows Download the synapse.<version>.windows.x64.exe installer from the latest release . It installs the app and CLI for your user account. Open a new terminal after installation to use synapse by name. See the Windows guide for build and verification steps. Install the CLI Open Settings in Synapse and choose Install CLI , or run the application binary once from a terminal and use: shell Copy synapse install synapse path synapse version The default destination is ~/.local/bin/synapse . If your shell cannot find it, add that folder to PATH and start a new shell. shell Copy export PATH="$HOME/.local/bin:$PATH" Synapse never overwrites an unrelated executable. A managed launcher has a sibling .synapsereceipt containing its digest so future installs can distinguish an update from a conflict. Enable shell integration In Settings → Shell environments , choose Enable shell hook . Synapse detects your default zsh, bash, or fish shell, installs the CLI if needed, and adds one marked block to its startup file. Open a new terminal after the setting changes. Shell Managed startup file zsh ~/.zshrc bash on macOS ~/.bash_profile fish ~/.config/fish/config.fish The app shows Needs repair if the managed block changes. Repair replaces only that block. Remove deletes only that block; already-running terminals retain the loaded hook until they close. Every changed existing startup file receives a .synapsebackup sibling and an atomic replacement. Connect tools See supported and custom harnesses to add a connector with a TOML descriptor or contribute one through a PR. On the macOS Synapse dashboard, each detected tool shows its installation and connection state. Choose Connect for Codex, Claude Code, pi, or Ainz. Setup performs two changes as one rollback-protected operation: It registers the installed Synapse executable as a user-level MCP stdio server with the single argument mcp . pi has no MCP client, so its step is pi install npm:synapse-pi instead: one package that starts the same server and offers the same tools. It creates SOUL.md when needed and appends or refreshes a small managed pointer in the tool’s global instruction file. Existing user content remains outside that block. The connected tool follows that pointer on launch, and the MCP server loads the same SOUL.md during initialization. The default guidance tells every connected tool to recall global plus current-project context, save confirmed reusable facts with project scope by default, use a Lean response first, and keep secrets out of memory. It also tells them to make the link visible: the first reply of a session opens with a single line such as Synapse connected · 3 memories recalled , or Synapse unavailable with a short reason when a call fails. The MCP server supplies that instruction itself, so guidance written before this behavior existed still produces the line without editing SOUL.md . Tool MCP store Instruction file Codex ~/.codex/config.toml ~/.codex/AGENTS.md Claude Code ~/.claude.json ~/.claude/CLAUDE.md pi ~/.pi/agent/settings.json ~/.pi/agent/APPEND_SYSTEM.md Ainz ~/Library/Application Support/ainz/mcp.toml ~/Library/Application Support/ainz/AGENTS.md Before changing a tool store or instruction file, Synapse creates a sibling .synapsebackup . If either half of setup fails, both files are restored. Use one shared guidance file Open Settings → Shared guidance . Open shared guidance edits the central SOUL.md . Sync pointers creates it if needed and refreshes every global pointer while preserving every line outside Synapse's managed blocks. If you want those global files to contain only those pointers, choose Consolidate guidance and confirm. Synapse moves their unmanaged content into SOUL.md , deduplicates simple shared rules, writes pointer-only global files, and keeps .synapsebackup siblings. This operation is never performed implicitly during connection setup. shell Copy synapse guidance show synapse guidance sync synapse guidance adopt --confirm Manual connection The app is the recommended setup path because it detects stale executable paths and repairs them. If you need to register the server manually, use the installed CLI path: shell Copy codex mcp add synapse -- ~/.local/bin/synapse mcp claude mcp add --scope user synapse -- ~/.local/bin/synapse mcp Then run synapse guidance sync so both global instruction files point to SOUL.md . Do not point an integration at a build artifact such as target/debug/synapse ; cleaning the repository would break the connection. Verify the connection Restart the connected tool so it reloads its MCP servers and instructions. Inspect its available tools. Synapse should expose remember , recall , readmemory , and vaultstatus . Ask it to remember one harmless confirmed convention, then recall it in a new session. Inspect the entry and its source in the macOS Memories screen, or use synapse memory list and synapse memory show <id> on Linux. If the dashboard shows a registered but disconnected state, the stored path is stale or its arguments differ from [\"mcp\"] . Choose Repair in the app or remove and re-add the manual entry."},{"title":"Supported and custom harnesses","description":"Connect a supported coding agent, describe your own with one TOML file, or contribute a connector.","path":"docs/harnesses/","text":"Supported harnesses A harness is the coding agent you run. Synapse ships connectors for Claude Code ( claude ), Codex ( codex ), pi ( pi ), and Ainz ( ainz ). Claude Code, Codex, and Ainz connect through MCP. pi uses the synapse-pi extension. Each connector declares shared guidance and skill locations, connection detection, and launch arguments. Claude Code and pi also have dedicated session integration. A custom descriptor does not acquire those hooks by declaring the same paths. shell Copy synapse tool list synapse tool show codex synapse connect codex Create a connector Install your harness and make its executable available on PATH . From your repository root, run: shell Copy synapse tool create mytool Synapse opens a commented template using VISUAL , then EDITOR , or vi if neither is set. Use an editor executable that waits until you finish editing. Replace the sample paths and arguments with your harness's actual settings. Synapse checks the descriptor before saving it to .synapse/tools/mytool.toml ; an invalid draft does not replace the saved file. Validation checks the descriptor structure, not whether your harness accepts its commands. Use synapse tool create mytool --user to save under the tools/ folder in Synapse's data directory. Run synapse path to locate that directory. A project descriptor overrides a user descriptor with the same name, and a user descriptor overrides a built-in. Use a lowercase name such as mytool ; it becomes the filename and the name passed to connection commands. You can share a project descriptor in your repository. Run the commands here from that repository's root so creation, inspection, and connection use the same project layer. Harness descriptors live in .synapse/tools/ ; .synapse.yaml configures project credential scopes. Descriptor example This example assumes a fictional harness named mytool with an mcp add command and a JSON MCP registry. Adapt it to your harness before connecting. toml Copy name = "My Tool" command = "mytool" [home] default = ".mytool" [paths] instructions = "{home}/AGENTS.md" settings = "{home}/settings.json" integration = "{home}/settings.json" skills = "{home}/skills" projectskills = ".mytool/skills" [connect] add = ["mcp", "add", "synapse", "--", "{server}", "mcp"] remove = ["mcp", "remove", "synapse"] [detect] format = "json" at = ["mcpServers", "synapse"] args = ["mcp"] [launch] prompt = ["{prompt}"] command names the executable. Connection arguments are separate array entries passed to that executable. home.default is relative to the user's home. Use {configdir}/mytool for the platform configuration directory, or set home.env if the harness supports a home-directory environment override. {home} in paths refers to that resolved harness directory. projectskills is relative to the project root. {server} expands to the Synapse executable. The harness must support the registration and removal commands you supply. detect.format accepts json or toml . detect.at names the key path to the Synapse server entry, whose command and arguments Synapse checks. Launch slots such as prompt , headless , config , and model are optional. Declare only flags your harness supports. A descriptor with an interactive prompt alone does not establish unattended mesh support. Use synapse tool show <name> to inspect a built-in example. A harness with a different transport, configuration schema, or lifecycle hook may need a code adapter; a descriptor cannot implement a protocol. Verify and update shell Copy synapse tool show mytool synapse connect mytool synapse status synapse launch mytool --print Restart the harness. Confirm it exposes Synapse's memory tools, then save and recall a harmless project convention. Inspect its instruction file for the shared guidance pointer. If it supports Agent Skills, test installing a skill with synapse skill install <skill> --tool mytool . To revise the descriptor, run synapse tool edit mytool , followed by synapse connect mytool --refresh . Add --user to the edit command for a personal descriptor. The show command reports the winning layer, which helps identify a project override. Test cleanup with synapse disconnect mytool and check that unrelated configuration remains intact. Deleting a descriptor with synapse tool delete mytool does not disconnect the harness; disconnect first if you are removing the integration. Contribute a harness PRs for additional harnesses are welcome at wess/synapse . You can use your custom connector while the PR is under review. Add the tested descriptor to crates/synapsecore/assets/tools/<name>.toml and add its embedded entry to BUILTINS in crates/synapsecore/src/agent/tool.rs . Include tests for the resolved paths, connection detection, registration and removal. Cover launch arguments and skill installation for the features you declare. The custom-harness integration test in crates/synapsecore/tests/cli.rs demonstrates the full connection lifecycle with a fake executable. State the harness version and operating systems tested. Check that setup and disconnect preserve unrelated settings and instructions. Update the README support table and this guide. Document limitations or any adapter code needed for special behavior. shell Copy cargo test --locked --manifest-path crates/synapsecore/Cargo.toml bun run site bun run sitecheck"},{"title":"Desktop app reference","description":"Use every Synapse screen with clear data boundaries, confirmation behavior, recovery paths, and shell-integration states.","path":"docs/app/","text":"Navigation and shared state The desktop app is a local control surface over the same SQLite database, vault, scope files, and tool configuration used by the CLI and MCP server. A column down the left moves between eight screens, grouped by what they answer: what Synapse is wired into, what is running, and what it is holding for you. Screen Use it for Material it can change Map See the whole store at once: which projects it holds anything about, where the weight is, and what a memory sits next to. Nothing. The map reads. Connections Detect and connect supported developer tools. Tool MCP configuration and managed instruction blocks. Memory Import, scope, search, inspect, correct, or remove durable context. Memory rows, scope metadata, and reversible import batches. MCP Keep one list of MCP servers, give it to every connected tool, and find servers in the registry. Server files in the Synapse data directory, their secrets in the vault, and each tool's MCP entries through its own CLI. Mesh Turn the agent mesh on and watch who has joined, what they report, and what they send each other. One local preference. The roster and messages are written by the agents themselves. Skills Keep one Agent Skills library, install it into every connected tool, and find skills other people published. The library in the Synapse data directory, and skill folders inside each tool. Vaults Manage labels, stored values, mappings, the value store itself, and approved project scopes. Vault metadata, stored values, and .synapse.yaml . Settings Manage shared guidance, recall, the mesh, self-improvement, the worker limit, appearance, CLI, and shell integration. SOUL.md , global pointers, local preferences, the CLI launcher, and one managed shell block. Success and error notices appear inside the active screen. The app does not require an account and does not send this state to a hosted service. Map Every other screen is a list, and a list answers what is stored rather than what it is about. The Map draws the store as a graph: one circle per memory, one larger circle per project, and lines between them. Projects come out as clusters, a correction comes out as a line to the thing it corrected, and a memory nothing else touches comes out looking like one — usually either the most interesting memory on the map or the one that should not be there. A point is sized by how much is tied to it and coloured by the project it belongs to. Click one to read that memory beside the map, with its scope, source, and date. A hollow point is a superseded memory: out of recall, still in the store, still restorable. The map is laid out in three dimensions, and the window lets you move around inside it: Do this Get this Drag Turn the cloud. Nearer points are drawn larger and brighter. Scroll Closer or further out, toward whatever the pointer is over. Shift-drag Slide the cloud within the frame. Click a point Read that memory beside the map. Reset view Put the camera back where it started. It appears once you have moved it. A plane is the wrong shape for a graph: a cluster with more neighbours than the plane has room for either overlaps itself or pushes everything else off the map, and both of those are the picture lying about the store. Turning it re-draws, never re-lays-out, so the shape you learned stays the shape it is. The plot is dark whatever theme you use. A glow is light added to what is behind it, and on a light background there is nothing to add to; the rest of the window stays in your theme. Three kinds of line, and only one of them is inferred: A memory and its project Recorded. Every memory hangs off the project it was stored for, or off global memory. A memory and the one that replaced it Recorded, and drawn in red. This is what supersession looks like from above. Two memories using the same uncommon words Inferred, and drawn faintest. It uses the same word list recall uses, so a search and a map cannot disagree about which words mean nothing. It is a resemblance, never a claim that two memories say the same thing. The map is bounded on purpose: it draws the newest 160 memories and says so whenever the store is larger. The layout is the same every time you open it, because a map is worth having when you have learned its shape. The map only reads Nothing on this screen changes anything. It opens the store when you arrive and when you press Refresh, and holds nothing between them. Connections The Connections screen reports memory count, database size, and how many supported tools are connected. It draws two lists: Connected , the tools wired in right now, above Supported , everything else Synapse can connect on this machine. A tool appears in one or the other, never both, and moves between them as you connect and disconnect it. Each tool row has one of four states: Not installed The command was not found on PATH . Set up is disabled until the tool is installed. Detected The tool executable is available but its exact synapse MCP entry is absent or stale. Choose Set up . Connected The stored executable and [\"mcp\"] arguments match the expected Synapse server. Update available Connected, and this release would connect it differently — its descriptor changed after the connection was made. Nothing is broken; there is a newer answer to apply. Choose Update . Set up registers the MCP server, creates SOUL.md when needed, and adds a marked pointer to that shared file in the tool's global instructions. For Claude Code it also installs the startup notice described below. Existing settings and text outside the managed block remain in place; changed files receive .synapsebackup siblings. If setup fails, Synapse restores the affected files. A connected Claude Code row also reports whether it announces Synapse at startup, with an Add or Remove control beside it. Adding it writes two hooks into that tool's own settings: a SessionStart hook, so the connection is stated beside its welcome message before the model has written anything, and a PreCompact hook that asks a long session to write down what it settled before its context is compacted away. The SessionStart hook also runs after a compaction, without the notice, to hand the session its memory back. A hook installed by an earlier release is brought up to date the next time the tool starts the MCP server. It also claims the status line when nothing else has. A status line you configured yourself is never replaced — the row says your status line kept instead. Removing takes back only what Synapse wrote. Either way the change applies to the next session that tool starts, and it never requires disconnecting and reconnecting. Keeping a connection current A connected row carries three controls, in increasing order of what they cost you: Update Applies this release's descriptor to a tool that is already connected. The registration is written again rather than skipped, because detection cannot tell a descriptor that moved from one that did not — it only knows an entry is present and points at Synapse. Nothing is removed, your skills stay installed, and the tool is connected throughout. This is what the Update available badge is asking for. Reset Disconnects the tool and connects it again. The bigger hammer, and the one that costs something: disconnecting removes the skills Synapse installed for that tool, and a reset puts the connection back rather than the library. Reach for Update first; use Reset when what is on disk has to go before the new answer can land. Remove Disconnects and leaves it that way: the MCP registration, the managed instruction block, the Claude Code hooks and status line, and any skill Synapse installed for it. A skill you wrote, your own words in an instruction file, and a status line somebody else configured all stay where they are. The row moves down to Supported . Synapse knows a descriptor moved because it records which one a connection was made from. A tool connected before this release has no such record, and reads as current rather than out of date — not knowing is not the same as knowing something is stale. Edit instructions and Edit config open the actual files in the built-in editor. See Tool integration files for their exact paths. Memory The import panel previews Claude and Codex separately. Import safe stores recognized project memory, skips anything credential-shaped, leaves source files untouched, and records a reversible batch. Review source opens the provider folder. Undo requires confirmation and preserves imported records that were edited or linked from another source. An empty search shows recent memory; a query searches the stored body and source and shows up to 100 results. Select an entry to inspect its ID, local creation time, full Markdown body, source, and visibility. Global makes it available everywhere; Project needs a project. The Project field shows the key the memory is filed under: a repository's remote such as github.com/owner/repo , or a folder path for a project without one. Leave it as it is to keep the memory where it is, or enter a folder path or another key to move it. Save changes replaces the selected body, source, and scope in place. Delete changes to Confirm delete before removing one entry. Wipe memories separately changes to Confirm wipe before deleting the entire memory table. A wipe does not affect vault labels, stored values, scope approvals, or settings. Recall optimization changes responses, not what this screen stores or displays. Read Memory and recall for search behavior, response budgets, and CLI equivalents. MCP The MCP screen has the same two tabs as Skills. Installed is a card per library server with the command it runs and a badge per tool. A server that still needs a secret says so with a Set button beside each one, and the value goes from that field into the vault; Install stays off until nothing is missing. Edit opens the server's file, and a draft that is not a valid server is not saved. Servers your tools already have appear under Already in your tools with Adopt , and an adopted server offers Swap to replace that tool's own entry, plaintext and all, with the library's. Search asks the official MCP Registry, which can take half a minute. Selecting a result shows the exact command it would become; Add to library keeps it without touching any tool. See MCP servers for the file format, the states, and what is and is not kept. Mesh The Mesh screen is off until you turn it on, here or in Settings. While it is off the screen explains the trade: the coordination tools are loaded by every connected tool, and that costs context in each session. Once on, it lists the agents that have joined with their role, project, and last reported work state; the background workers running under a Synapse session; and the recent messages between them. Nothing on this screen changes what agents do — it reports. Refresh re-reads the database, which is also what opening the screen does. See Agent mesh for roles, teams, and the command line. Skills The Skills screen lists your library, and beside each skill a badge per connected tool saying whether that tool has it, has an older copy, or has one Synapse did not write. Install on a skill copies it wherever it is missing or behind; Install all does the lot. Skills a tool already has that the library does not know about appear under Already in your tools with an Adopt button, which copies one into the library and starts managing it. A skill Synapse did not install is never overwritten or deleted from this screen — it is reported instead, and the result line says how many were left alone. The Search tab asks skills.sh. Results are listed down the left; selecting one reads its whole SKILL.md into the panel beside them without keeping anything, and Add to library brings it in waiting for review, so it shows up on the Installed tab with Approve and Turn down rather than in any tool. See Skills for the format, the exact folders, and what each state means. Vaults and scopes Create a vault, select it, then provide a label, environment name, and value under Add a secret . Save to the vault writes the value straight into the value store while brain.db keeps only its label, environment name, account reference, and scope state. The app never shows a saved value again. Project only requires an approved YAML mapping; Global makes that environment name available without a project mapping. Copy puts one value on the clipboard. It is the only way a stored value comes back out, and it never passes through the window. Replace reads the current Secret value field and overwrites the stored value without changing its reference. Forget requires Confirm , then removes the stored value and its metadata. Delete vault is available only when the selected vault is empty and requires a second confirmation. A badge beside the counts names the store this machine keeps values in — Encrypted vault.db or macOS Keychain — and the button next to it moves every value into the other one. That takes two clicks: the first asks, the second migrates. See Where values live for what the two stores protect. Under Project and folder scopes , choose a directory, create or edit its .synapse.yaml , inspect the reported state, then choose Approve only after reviewing the exact file. Any later edit invalidates that digest and requires another review. Secret values never enter YAML. See Vaults and scopes for resolution order and both process boundaries. Settings Shared guidance Open shared guidance edits SOUL.md . Sync pointers refreshes both global files without removing unmanaged content. Consolidate guidance requires confirmation, moves existing global text into the shared file, and leaves backups before making both global files pointer-only. Recall optimization Full, Balanced, and Lean change the shared MCP response limit and character budget. Original memory remains untouched. Balanced is the default; exact limits are documented in Response budgets . Agent mesh Off and On switch the coordination tools for every connected tool. Off keeps the tool list at its smallest, which is why it is the default. Tools already running keep the tool list they started with, so the change applies the next time each one starts. The same switch is on the Mesh screen. Self-improvement Off and On decide whether connected sessions get teach and revise . Off is the default, for the same reason the mesh is: two more tool definitions cost context in every session that loads them. A skill an agent writes waits on the Skills screen and reaches no tool until you approve it, so leaving this on never changes how a session behaves without you having read what changed it. Background workers The most agents one supervisor may run at once. Every worker is a separate session on the account you already pay for, so this is a spending limit as much as a performance one. Whatever you choose is clamped to a ceiling in code, so a mistyped number cannot buy an unbounded fleet, and synapse settings workers <count> takes any value up to it. A supervisor already running picks a change up on its next spawn. Appearance System follows the current macOS appearance as it changes. Light and Dark pin the app to that mode. The preference is stored locally. Command line The status is Installed, Not installed, or Conflict. Install CLI places the managed launcher at the displayed path. Synapse refuses to overwrite an unrelated executable; resolve a Conflict deliberately before trying again. Shell environments Command scoped always remains available through synapse run -- <command> . Automatic directory loading is an explicit opt-in for the detected default zsh, bash, or fish shell. Only that detected shell is changed. Status Control Result Not enabled Enable shell hook Installs the CLI if needed and adds one marked startup-file block. Enabled Remove hook Removes only the marked block and leaves neighboring startup content intact. Needs repair Repair hook or Remove Replaces or removes only the changed managed block. Unavailable Unavailable No supported default shell or safe startup path could be detected; no file is changed. Open a new terminal after enabling, repairing, or removing the hook. Existing terminals retain the integration they already loaded. Ambient values are readable by every child of an activated shell; use the command-scoped mode for a sensitive one-off process. Startup files stay user-owned Synapse backs up and atomically rewrites the detected startup file, follows an existing symlink, and refuses malformed or duplicate managed markers instead of guessing what to replace. Editors, local data, and recovery The built-in editor handles SOUL.md , supported tool instructions, TOML or JSON configuration, and YAML scope files. Structured formats must validate before saving. Changed files are backed up and replaced atomically while existing permissions and symlinks are preserved. If an editor contains unsaved changes, Close and application quit are blocked until you choose Save or Discard. Saving a scope refreshes its state but does not approve it; review the result and choose Approve separately. Open data folder on the Connections screen reveals the directory containing the local database. It does not create a backup and editing database files by hand is unsupported. Use Data lifecycle for integrity checks, validated exports, exclusive restore, and recovery behavior."},{"title":"Memory and recall","description":"Know what becomes durable, how search works, how to correct stored context, and what each response budget changes.","path":"docs/memory/","text":"What belongs in durable memory Good memory remains useful after the current conversation ends. Store confirmed decisions, recurring preferences, corrected assumptions, naming conventions, project constraints, and facts whose source is clear. Shared guidance lives in SOUL.md . Each connected tool's global instruction file contains a managed pointer to it, and the MCP server loads the same file during initialization. Both tools therefore receive the same recall, storage, scope, and token-budget policy. Do not store transient task status, speculative ideas, full conversation transcripts, secret values, or instructions that attempt to override the current request or repository guidance. Synapse is the canonical durable memory store; it does not create separate Markdown files for individual memories. shell Copy printf '%s ' 'Use Bun for JavaScript tasks in this repository.' \\ | synapse memory add tooling memory add and memory edit read the body from stdin. This keeps long content and shell metacharacters out of command arguments. Empty or whitespace-only memory is rejected. Credentials are taken out before anything is stored, whether the memory comes from a tool, the CLI, the desktop editor, or an import. A value assigned to a secret-sounding name ( GITHUB_TOKEN=… , --password=… ), a password inside a URL, a Bearer token, a provider token with a known prefix ( sk- , ghp_ , AKIA , xoxb- , and others), and a PEM private key block are each replaced with [redacted] , and anything between <private> and </private> is removed. The variable name stays, so the memory still says what to set. A reference such as GITHUB_TOKEN=$(gh auth token) is kept as written. remember reports how many parts it removed. Values belong in the vault . Global and project scope Project memory is the default. Synapse resolves the supplied path to its repository or approved project root and returns that memory only alongside global memory for the same project. Unrelated project memory stays out of MCP recall. A repository with an origin remote is filed under that remote, normalized to host/owner/repo with any credentials dropped, rather than under the folder it is checked out in. Every checkout of github.com/you/app shares one project memory, wherever it is: a second clone, a worktree, or the same repository after you move or rename its folder. A folder without a remote is keyed by its path. Memory an earlier release stored under a checkout's path moves to the remote's key the first time this release opens the store; a path whose folder is gone stays as it was. shell Copy synapse memory show 24 # Project: github.com/you/app Use global scope only for preferences and conventions that should follow you everywhere. The desktop editor can move a record between scopes. The CLI accepts an explicit scope when adding: shell Copy printf '%s ' 'Use Bun in this repository.' | synapse memory add tooling --project . printf '%s ' 'Prefer concise status updates.' | synapse memory add preferences --global Import existing memory The Memory screen previews Claude and Codex independently, maps each item to its project when possible, and imports safe entries into the same scoped store. Source files are read-only: Synapse never edits or deletes them. Claude imports leaf Markdown files from project memory directories and skips MEMORY.md indexes. Codex imports processed memory rows from a recognized local memory database and maps thread IDs to project roots through the local thread catalog. Full conversations, session history, tasks, settings, authentication files, and global instruction files are not imported. Credential-shaped entries are held for review. Their content is hidden in previews and is not imported by the app. Repeated imports are idempotent. Exact scoped duplicates are linked instead of copied, and each batch can be undone without removing a memory that was edited or shared by another source. shell Copy synapse memory import claude synapse memory import claude --confirm synapse memory import codex --confirm synapse memory import markdown ./notes --confirm synapse memory imports synapse memory undo <batch> --confirm Review before overriding the guard The CLI can include flagged entries only with both --include-flagged and --confirm. Review the named source file first; memory is plain text and available to connected tools. Search and recall Memory is stored in SQLite FTS5 and searched two ways at once. A word index, stemmed, finds a word in another form: deploying finds a memory that says deploys . A fragment index, by three-character pieces, finds part of an identifier or a word the stemmer does not know: hashpw finds scripts/hashpw.sh . Each ranks the query on its own and the two lists are merged by reciprocal rank, so a memory both like comes first. The body and the source are both searched, the source at under a third of the weight, so a memory filed under tooling is found by that word without outranking one that says it. An empty query returns recent entries. Connected tools are instructed to begin with a focused query, the smallest useful result limit, and a Lean response. When a result has to be shortened to fit the response budget, it is cut to its opening line if that line contains a word the query matched. If it doesn't, the passage around the match is sent instead, marked with … at both cut ends. Either way the result is marked abridged , and readmemory returns the whole text. json Copy { "query": "project naming conventions", "limit": 4, "budget": "lean", "project": "/Users/example/project" } The optional per-call budget may be full , balanced , or lean . It can only make the response smaller than the mode selected in Settings; a tool cannot override a user-selected Lean ceiling with Full. shell Copy synapse memory list "Bun JavaScript" synapse memory list --json synapse memory show 24 synapse memory show 24 --json Measuring recall Whether a change to how memory is written or searched made recall better is a question with an answer. Write down questions an agent asks, with text a useful answer contains, and score them against the store: toml Copy [[case]] query = "which javascript runtime" project = "/Users/example/project" expect = ["bun"] [[case]] query = "where do credentials live" expect = ["vault", "keychain"] # Nothing stored answers this, so the best recall returns nothing. [[case]] query = "kubernetes autoscaling" shell Copy synapse memory eval cases.toml synapse memory eval cases.toml --budget lean --fail-under 0.8 Each case runs through the same scoped recall a tool gets. The report gives the share of questions whose answer ranked first, the share whose answer came back at all, mean reciprocal rank, shown (the share whose answer text survived the response budget, which --budget sets for the run), and noise : how many memories came back for questions nothing answers, each of which an agent reads for nothing. --fail-under exits non-zero when the found share is below the floor, so a cases file can guard recall in CI. Matching is by text, case-insensitive, so one file keeps working across machines and corrections. The CLI lists up to 100 matching memories across the local store for management. Each record contains an integer ID, exact body, source, global or project scope, project key, and Unix timestamp. JSON output is suitable for local automation. Ranked search drops words that appear in nearly every memory — the , are , where — because a memory that matches only on one of those is a confident wrong answer, and an agent acts on it. Words that carry meaning in a preference, including not , never , and use , are kept. Two commands exist for when that is the wrong behaviour or the wrong result: shell Copy synapse memory grep -- --no-verify synapse memory list where are the credentials --explain memory grep matches the characters you give it and nothing else, which is what you want for an identifier, a flag, a path, or a word the ranked search treats as noise. --explain answers the other question — why a memory you know is stored did not come back: text Copy Query: where are the credentials Mode: search Expression: "credentials" Fragments: "credentials" Searched: credentials Dropped: where, are, the (matches nearly every memory) Matches: 1 0.0328 31 global vault Credentials live in the vault, never in the repository. The score is the fused rank, and higher is better. A memory ranked first by both indexes scores about 0.033; one ranked first by only one of them scores about half that. Inspect and correct The desktop Memories screen searches the same store and lets you inspect, edit, or delete individual entries. CLI edits replace the body and optionally the source: shell Copy printf '%s ' 'Use Bun unless a task explicitly requires another runtime.' \\ | synapse memory edit 24 tooling synapse memory show 24 Editing changes the stored original. Recall optimization does not. See the whole store at once Search answers a question you already have. The Map answers the one you do not: what is in here. It draws every memory as a circle, clustered by project, with a line to whatever replaced it and a fainter line between memories that use the same uncommon words. Click one to read it. It is laid out in three dimensions: drag to turn it, scroll to move closer, and click a point to read it. It reads the newest 160 memories and says so when the store is larger, without changing anything. Correcting a memory A convention changes. Something you stored last month is now wrong. Adding the new version on its own leaves two memories contradicting each other, both are recalled, and the ranking decides which one a tool acts on — possibly the one you had already retracted. Say which replaced which: shell Copy printf '%s ' 'Deploys run from the release branch, after the tag is signed.' \\ | synapse memory add deploys --global synapse memory supersede 12 47 A connected tool does it in one call, passing the id that came back from its own recall : json Copy { "content": "Deploys run from the release branch, after the tag is signed.", "scope": "global", "supersedes": [12] } Nothing is deleted. Memory 12 keeps its id, stays in memory list and the dashboards marked as replaced, still says what replaced it, and comes back at any time: shell Copy synapse memory show 12 synapse memory restore 12 What changes is only what recall can see. A superseded memory stops being returned to tools and stops counting toward the number a session reports — the count a tool announces is the count it can actually draw on. Deleting the replacement restores what it replaced on its own, so a correction can never leave the older version hidden behind an id that no longer exists. Choose between the three: edit when the memory was badly worded and there is nothing to keep, supersede when the old version was true and stopped being true, and delete when it should never have been stored. Supersession stays on this machine Sync carries a memory's content, not what replaced it. A memory superseded here arrives on another machine live. Until the wire format grows an operation for it, correct it on each machine you sync to. Response budgets Mode Result limit Character budget Transformation Full 25 Unlimited Returns stored formatting without compaction. Balanced 8 6,000 Compacts prose whitespace, preserves fenced and indented code, removes exact duplicate bodies, and replaces any memory too large for the remaining budget with its opening sentence, or with the passage its query matched. Lean 4 2,800 Uses the same non-destructive compaction with a smaller response. The response reports the optimization mode actually applied. Choose the ceiling in Settings → Recall optimization , or use the CLI: shell Copy synapse settings show synapse settings optimize lean synapse settings optimize balanced A memory that will not fit in what is left of the budget is returned as its opening sentence, marked abridged , rather than cut off wherever the character count landed. When a query found it by something its opening does not say, the passage around the match is returned instead, with … at both cut ends; see Search and recall . Half a memory reads exactly like a whole one — never deploy from main unless is a rule with its condition amputated — and one long memory at the top of the results no longer costs you every result under it. A tool that receives an abridged memory uses readmemory with its id and the same project to read the exact body in bounded pages. Originals remain intact Balanced and Lean transform only the MCP response. The database and memory-management views keep the complete stored body, abridged results included. Similar wording is not enough to discard a memory: negation, numbers, and short identifiers can change the meaning. Distinct bodies remain eligible until explicitly superseded. Search keeps non-Latin terms instead of treating them as empty queries. Destructive actions Deleting one memory, undoing an import, and wiping every memory require explicit confirmation. The desktop app presents a separate confirmation. A wipe removes memory and import history but leaves guidance, vault labels, stored values, scope approvals, and settings intact. shell Copy synapse memory delete 24 --confirm synapse memory wipe --confirm Before a large cleanup, create a portable snapshot with synapse data export . Use a wipe when the goal is to clear context while retaining the vault setup; use a database restore when the goal is to return the entire Synapse state to an earlier snapshot."},{"title":"MCP tool reference","description":"Run the local stdio server and understand the exact request, response, and value boundary of every exposed tool.","path":"docs/mcp/","text":"Server Synapse implements an MCP stdio server. A tool launches the same signed or installed executable with the mcp argument: shell Copy ~/.local/bin/synapse mcp The process uses stdin and stdout for protocol messages. Do not wrap it in a command that writes banners or shell setup output to stdout. The server opens the same local database as the desktop app and holds a shared lifecycle lock while connected. Session start Connecting Claude Code also installs a SessionStart hook that runs synapse session . It does two things a tool cannot do for itself: it prints a line in the terminal before the model has written anything, and it puts this project's memory into the session's context before the first turn. A connected pi runs the same command from its extension and shows the same two halves. shell Copy synapse session --json json Copy { "systemMessage": "Synapse connected · 128 memories", "hookSpecificOutput": { "hookEventName": "SessionStart", "additionalContext": "Synapse is connected and holds 128 memories for github.com/example/project. …" } } additionalContext carries the memories themselves, most recent first, under the same scope rule recall uses: everything global, plus everything stored for this project, and nothing from any other project. A session therefore opens already holding what the project has decided, rather than being asked to go and look. That matters because the alternative was guidance. Asking a model to call recall before it starts is an instruction it may or may not follow, and a session that skipped it worked from nothing while still reporting a connection. Guidance still asks for recall , because a focused query in the middle of a task is the case a session-start recall cannot cover. Behavior Detail Budget Recalls under a balanced ceiling. A per-call budget can only shrink your configured one, so a store set to lean still returns lean . Scope Global memory plus the project the calling tool reports through cwd or workspace.current_dir , falling back to SYNAPSE_PROJECT_DIR . Empty store No block is injected. The context asks the tool to call remember once something durable is settled. Failure Reports Synapse unavailable with a short reason and tells the model not to claim a connection that is not there. The hook never exits non-zero, because a failing hook is noise in your terminal. Trust Recalled content is labelled as context, never as instruction. It does not override the current request, repository guidance, or what you say next. After compaction The hook runs again with source set to compact . It prints no notice and hands the memory back, since the compaction dropped it. See Before compaction . Codex has no equivalent hook Codex does not expose a session-start hook, so a Codex session still opens by calling recall itself as the shared guidance in SOUL.md asks. Everything else — the tools, the scope rule, the response budget — is identical. pi reaches the server through a package pi has no MCP client, so its connection is the synapse-pi package. The extension in it starts the same server, registers whatever tools that server advertises, runs the same session-start recall, and shows the same status line. Turn the mesh on and its sixteen tools appear in pi too, on the next start. Before compaction The other end of the same session. When a long session is about to be compacted, everything it worked out that nobody wrote down is about to stop existing — and it is the only moment where not having written something down costs immediately. Connecting Claude Code installs a PreCompact hook that runs synapse compact ; a connected pi runs the same command from session_before_compact . shell Copy synapse compact json Copy { "hookSpecificOutput": { "hookEventName": "PreCompact", "additionalContext": "Synapse holds this project's durable memory, and this session is about to be compacted…" } } It asks for an explicit list of what the session settled and is not already stored, and for a remember call for each one. It deliberately recalls nothing : the context window is being reclaimed, and spending it re-injecting memory the session already had is the opposite of what a compaction is for. Afterwards, Claude Code starts the session hook again with source set to compact . That run prints no connection notice, since you saw it when the session began, and hands the memory back: the same recent memory the session started with, re-read, so anything it remembered before compacting comes back too. A hook installed by an earlier release is updated to fire on compaction the next time the MCP server starts. The compaction itself is never blocked, cancelled, or rewritten. A memory tool that traded a session's whole context for a reminder would be a worse bargain than forgetting. remember Stores a durable fact, decision, preference, convention, or correction. json Copy { "content": "Use small focused modules.", "source": "synapse", "scope": "project", "project": "/Users/example/project", "supersedes": [18] } Field Type Required Meaning content string Yes The durable text. Empty or whitespace-only content is rejected. Credentials and <private> sections are removed before it is stored; see What belongs in durable memory . source string or null No An origin such as a project path, repository name, or topic. scope project or global No Defaults to project. Use global only for context that should appear everywhere. project string or null For project scope Absolute working-project path. Synapse resolves nested paths to the project root, then files the memory under that repository's origin remote ( host/owner/repo ) when it has one, and under the root's path when it does not. supersedes array of integers or null No Ids of memories this one replaces — usually ids that came back from an earlier recall . Each stops being recalled. Nothing is deleted. An id that no longer resolves is skipped rather than failing the write. json Copy { "id": 24, "stored": true, "superseded": [18] } redacted appears only when something was taken out before storing, and says how many parts: a credential or a <private> section. The stored text is then not the text that was sent, so do not rely on the removed value being in memory. The store and the replacement happen in one transaction. Without supersedes , a correction is a second memory contradicting the first, both come back from the next recall, and the ranking decides which one an agent acts on — including the version its own author had already retracted. recall Returns durable context relevant to a query. A word matches in any form, so deploying finds deploys ; part of an identifier matches too, so hashpw finds scripts/hashpw.sh ; and a memory's source is searched at a lower weight than its body. An empty query returns recent memory. Search and recall has the details. json Copy { "query": "module structure", "limit": 8, "budget": "lean", "project": "/Users/example/project" } Field Type Required Meaning query string Yes Words or a phrase describing the context needed. Use an empty string for recent entries. limit integer or null No Requested result count. Defaults to 8. The active response budget may lower it. budget full , balanced , or lean No May reduce the configured response ceiling but can never enlarge it. project string or null No Absolute working-project path. Results include global memory plus this project, in any checkout of the same repository, and exclude other projects. json Copy { "optimization": "lean", "memories": [ { "id": 24, "body": "Use small focused modules.", "source": "synapse", "scope": "project", "project": "github.com/example/project", "created": 1785250000 } ] } Two fields appear only when they are true of the result. abridged marks a memory returned in part, because the response budget could not carry the rest. The part is its opening sentence when that sentence holds a word the query matched, and otherwise the passage around the match, cut with … at both ends — use readmemory with its id and the same project before acting on the part you cannot see. superseded never appears here at all, because a replaced memory is not recalled. readmemory Read one memory by id after recall identifies it. The body is an exact page of stored text, including code and whitespace. No model or additional index is involved. json Copy { "id": 24, "project": "/Users/example/project", "offset": 0, "budget": "lean" } Field Type Required Meaning id positive integer Yes Memory id from recall. project string or null No The same absolute project path used for recall. Scope resolution is identical. offset nonnegative integer or null No UTF-8 byte offset, initially zero. Use the preceding page's next value to continue. budget full , balanced , or lean No Can reduce the configured ceiling. Lean pages contain at most 2,800 bytes; Balanced and Full pages at most 6,000 bytes. json Copy { "optimization": "lean", "memory": { "id": 24, "body": "Use small focused modules.", "source": "synapse", "sourceabridged": false, "scope": "project", "project": "github.com/example/project", "created": 1785250000, "offset": 0, "next": null, "total": 26 } } next is null at the end. Otherwise, pass it as offset with the same id and project. total is the stored body's UTF-8 byte length. Byte limits stay within the character ceiling and never split a character. An offset inside a character or beyond the body is an error. The source label has a separate 240-byte allowance; sourceabridged says whether it was shortened. Scope, project, creation time, and id retain the memory's provenance. Metadata and JSON framing are additional to the body budget. A missing, superseded, or out-of-scope id returns {\"optimization\":\"lean\",\"memory\":null} under Lean. Each page reads the current stored memory. If the memory is edited while reading, restart at zero; pages do not hold a historical snapshot. Hidden history remains available to its owner through synapse memory show and synapse memory restore . vaultstatus Lists active environment-variable names and scope trust state for a folder. It never returns secret values and cannot inject them into the connected tool. json Copy { "path": "/Users/example/project" } path is optional. Resolution falls back to SYNAPSE_PROJECT_DIR , then the server process’s current directory. json Copy { "path": "/Users/example/project", "backend": "encrypted", "available": ["DATABASE_URL"], "unavailable": ["deploy.RegistryToken"], "elsewhere": 4, "ambient": "ready", "shell": "zsh", "scopes": [ { "path": "/Users/example/project/.synapse.yaml", "scope": "project", "trusted": true, "changed": false, "env": ["DATABASE_URL"], "denied": [], "error": null } ], "warnings": [], "note": "Values stay in the vault and never in a response. A name under available resolves for this folder; one under unavailable is named by a scope file here and needs it approved first. elsewhere counts secrets held for other folders, which are never named." } Metadata, not a secret channel The names in available tell a tool what a scoped command or activated shell could receive. vaultstatus never reads the corresponding values and cannot change a connected tool’s environment. backend names the store this machine keeps them in, which is a fact about the machine and not a way to reach one. Nothing here reports a credential belonging to a folder other than the one asked about. unavailable names the secrets this folder asks for and cannot reach, as vault.name . Only ones a .synapse.yaml on the way down to the folder names, so every name in it is already written in a file the caller can open; a long list is truncated and the remainder joins the count. Everything else this machine holds is counted by elsewhere and never named — this tool reaches every connected tool, and an agent working on one project has no business being handed the names of another project's credentials. The pair exists because an empty available is otherwise the same answer whether nothing is stored or nothing is approved here, and those have different fixes. When a folder resolves nothing and the store is not empty, warnings says so and names the step that would change it — synapse scope init , then naming the secret in the env: block, then synapse scope trust . teach and revise Two tools that appear only while synapse settings learn on is set. They are how a session writes down a procedure it worked out, and corrects one that turned out wrong. Both write to the Synapse skill library and to nothing else. teach takes a name , a one-line description , the instructions as Markdown, a scope of project (the default) or global , the absolute project root, and a note for you saying why it was worth keeping. Synapse writes the frontmatter itself rather than accepting it, so a model never gets to invent YAML keys or a name that disagrees with its own directory. The result always says the same thing: stored, and waiting for you. revise takes the name , the corrected instructions in full, and a note saying what was wrong with the version it replaces. It returns the revision id holding the old text and the tools the correction reached. Teaching is free; installing is the decision A taught skill is in the library and in no tool until you approve it, so a session can never change how the next one behaves. A revision is the exception and reaches the copies Synapse installed — you already agreed to that skill being loaded, and a correction that never arrives leaves every session running the version that was wrong. What it said before is kept either way. See the skills guide . Mesh tools Three tools are always present. Sixteen more appear only while the agent mesh is switched on, and two more while self-improvement is, because a tool definition costs context in every session that loads it. They let connected sessions register under a name, message each other directly or by channel, park on wait until work arrives, report and watch work state, and start or stop background workers. The guidance explaining them is sent with them and withdrawn with them, so the tool list and the instructions can never be out of step. That is true of both switches. See the agent mesh guide for the full list and what each one is for. Operational behavior Tool errors are returned as readable strings. Protocol messages remain on stdio. The server identifies itself as synapse using the application version and advertises tool capability only. Opening the database runs integrity and relationship checks, applies numbered migrations, and secures database permissions. A running MCP process holds a shared database lock. synapse data restore requires an exclusive lock and therefore refuses while any connected server or the desktop app is using the database. Connected tools should recall before decisions that depend on project history and remember only stable confirmed context after it is established. A Claude Code session starts with that memory already in context; see Session start ."},{"title":"Agent mesh","description":"Let connected coding tools message each other, split up a job, and park for free between tasks, using the same local database as your memory.","path":"docs/mesh/","text":"What the mesh is Every tool you connect to Synapse already runs its own copy of the same local server. The mesh gives those sessions a way to reach each other: a supervisor breaks a job into tasks, hands them out by name or by channel, and collects the results, while every idle agent waits at almost no cost. There is no daemon, no port, and no token. The message bus is the same local database that holds your memory, so a message between two agents never leaves your Mac and is inspectable with the same tools as everything else Synapse stores. The mesh is off until you turn it on. Its tools are loaded into every connected session, and that costs context in each one, so Synapse does not add them to a setup that will not use them. undefined Turn it on Use Settings → Agent mesh in the app, or the command line: shell Copy synapse settings mesh on A tool that is already running keeps the tool list it started with. Restart it, or open a new session, to pick the change up. The same is true when you turn the mesh back off. How agents join Switching the mesh on makes the tools available; it does not put anyone on the mesh. A session joins when it calls register with a name of its own, which happens when you ask it to work with other agents or when Synapse launched it with a role. Tool What it does register Join under a unique name. Called once, before anything else. send , post , broadcast Message one agent, a channel, or everyone. join , leave Subscribe to and unsubscribe from a channel. wait Block until work arrives. This is how an agent stays reachable while doing nothing. inbox Take whatever is waiting right now, without blocking. reportstatus , waitstatus Report working, blocked, or done with a one-line note saying what you are doing, and block until a teammate reaches one of those. agents , channels , whoami See who is here, what channels exist, and your own place in it. spawn , workers , stopworker Grow, inspect, and shut down a team of background workers. A parked wait returns an empty list after a few idle minutes and the agent simply calls it again, so an idle teammate costs one tool call every few minutes rather than a loop that spins. Roles A role is the durable identity an agent launches with: a brief describing what it owns and how it coordinates, plus optional defaults. It is separate from a task, which is the one-off assignment. Synapse ships with supervisor , overseer , worker , frontend , backend , reviewer , devops , and qa . shell Copy synapse relay role list synapse relay role show frontend synapse relay role create reviewer # writes into this project synapse relay role create reviewer --user # writes into your own layer Roles resolve from the project first, then your own layer, then the built-ins. A role saved into a project lives in .synapse/roles/ and travels with the checkout. Editing a built-in copies it down into a layer you own, so the shipped templates stay intact. toml Copy channels = ["frontend"] tool = "claude" # model = "claude-opus-5" # driver = true # stay interactive instead of parking # tools = ["Read", "Edit", "Bash(git:*)"] # pre-granted tool rules description = """ You own the frontend. Follow the existing component conventions and report blockers to the supervisor. """ Teams A team is a named roster. Opening one launches every member at once: the first is the lead and runs in your terminal so you have someone to steer, and the rest run in the background. shell Copy synapse relay team list synapse relay team open web Closing the lead stops its team. The command that opened the team is the process supervising it, so nothing is left running behind you. toml Copy [[member]] name = "lead" role = "supervisor" [[member]] name = "backend" role = "backend" Background workers A supervisor can also grow its own team as it works, by calling spawn . A worker started that way runs headless, registers itself, and parks on wait until it is given something to do. Because nobody is watching a headless session, it runs with its own tool's permission prompts bypassed. Workers belong to the session that started them, for as long as that session lives. A worker that exits is restarted with a backoff that grows, and one that never manages a healthy run is retired rather than restarted forever. At most eight run at once. shell Copy synapse relay launch backend --role backend --task "build the login API" synapse relay ps synapse relay kill backend A launched agent is given the project folder it should work in, and reaches Synapse through the same connection it already has. Nothing generates a credential, and no secret value is ever passed to a launched agent. undefined Driving a team yourself Everything above puts a lead agent between you and the work: you brief the lead, the lead hands out tasks and relays the answers back. That costs the lead's context on every message and makes it a bottleneck for a job it is not doing. synapse mux puts you on the mesh instead. You get a name on the roster and the same messaging every agent already has, so you can address any agent directly. shell Copy synapse mux --team pair @backend the created column needs a default, not a backfill #build freezing the schema in ten minutes ! stop and report where you are /focus backend and the index too A bare line goes to whoever is focused, so a back-and-forth with one agent reads like a conversation. /help lists the rest: /agents , /workers , /log <name> for what a worker has been doing, /kill , and /quit . Leaving takes the workers it started with it. Or hand it to one agent Picking a roster before you understand the job is its own small chore, and most jobs do not need four agents. The overseer team is one agent and nothing else: shell Copy synapse mux --team overseer @overseer get the release notes written and the changelog updated You are still on the roster as yourself, so nothing is interposed — you can address any worker it starts without going through it. What changes is that you describe an outcome rather than handing out the pieces, and it grows a team with spawn only when the work turns out to want one. Its brief is explicit that starting a worker is not free and that eight is the ceiling. It launches with Claude Code unless you say otherwise. synapse relay team create overseer --user copies the file into your own layer, where a tool line names any connected tool instead. The more useful direction is the other one. A headless worker runs with its permission prompts bypassed, so when it reaches a decision it should not make alone, it has had nobody to ask — and has had to guess. With a person on the roster it can send you the question, report itself blocked, and wait for the answer. Agents can tell a person from an agent. A roster entry for a human is marked as one, and connected tools are told to ask them questions and never delegate work to them. undefined Messages reach an agent at its next check, not as an interrupt. One parked between tasks answers in about a second; one in the middle of a long build sees you when it comes back. The roster shows which is which. undefined Watching the mesh The Mesh page in the app shows who has joined, what each one last reported, which workers are running, and the recent traffic between them. The same is available from the terminal: shell Copy synapse relay status synapse relay agents synapse relay channels synapse relay feed --follow Every command takes --json for scripting. What an agent is doing A state on its own tells you an agent has not stalled. It does not tell you what it is working on, and for a headless worker there is no terminal to look at. So reportstatus takes a note as well as a state, and that note is what the roster shows: shell Copy synapse relay agents lead supervisor online working splitting the migration into three tasks /work/api backend backend online blocked need the staging database name /work/api frontend frontend online working rewriting the auth middleware /work/api The note is one line, bounded, and kept when a later report does not carry one — an agent that says working twice has not stopped doing the thing it described the first time. Reporting an empty note clears it, which is how an agent going idle drops a note about work it has finished. waitstatus returns the note with the state, so a supervisor blocking until a worker reaches blocked is told what it is blocked on in the same reply. Without that it would have to go and read the worker's log to find out, which is the thing the note exists to avoid. Notes are guidance, not a guarantee Writing one is something connected tools are told to do, not something Synapse can enforce inside another tool's session. An agent that reports a state without a note leaves the previous one standing, so a stale note is possible; the roster's last-seen time is what tells you whether the agent itself is still live. Limits A message from another agent is information, not instruction. Connected tools are told to treat mesh traffic as untrusted input that never overrides you, your shared guidance, or repository rules. Broadcast and channel history is not replayed. A new agent sees only what is sent after it joins, so start your workers before you brief them. A direct message is held for an agent that has not registered yet. Delivery is at least once. A reply lost in flight is delivered again rather than dropped; a duplicate is possible on that rare path. An agent that stops answering leaves the roster within about a minute and a half, and a session that closes takes its name with it. Agents work on your real machine with your real files. A background worker runs with its permission prompts bypassed, because there is no terminal in which to answer one."},{"title":"Skills","description":"Keep your Agent Skills in one library, install them into every connected tool, and let a session write down a procedure it worked out — for you to approve before it reaches anything.","path":"docs/skills/","text":"Why a library Claude Code, Codex, pi, and Ainz all read the Agent Skills open format, and each reads it from its own folder. A skill you want in all of them gets copied by hand as many times, and the copies start drifting the first time you improve one of them. Synapse keeps one copy. You edit it in one place, install it into every connected tool, and the app tells you which copies have fallen behind. Where skills go Place Path The Synapse library <data>/skills/<name>/SKILL.md … for one project <data>/skills/@<project>/<name>/SKILL.md Claude Code ~/.claude/skills/<name>/SKILL.md Codex ~/.agents/skills/<name>/SKILL.md pi ~/.pi/agent/skills/<name>/SKILL.md Ainz ~/Library/Application Support/ainz/skills/<name>/SKILL.md A project's own <project>/.claude/skills/ , .agents/skills/ , .pi/agent/skills/ , .ainz/skills/ Codex reads personal skills from the shared <code>~/.agents/skills</code> folder rather than from its own home. <code>~/.codex/skills</code> holds the set Codex ships with, and Synapse does not write there. undefined The format A skill is a directory with a SKILL.md at its root. The frontmatter needs a name that matches the directory and a description saying what the skill does and when to reach for it. Everything after the frontmatter is the instructions. markdown Copy --- name: release-checklist description: Walk the release checklist for this project. Use when cutting a release, tagging a version, or when the user asks what still needs doing before shipping. --- ## Steps 1. Run the full test suite. 2. Bump the version. 3. Write the release notes. A skill can carry more than its SKILL.md — scripts/ , references/ , and assets/ travel with it, and Synapse copies the whole directory. Agents load only the name and description until a task calls for the skill, so a long reference file costs nothing until it is needed. A description containing a colon followed by a space has to be quoted, or YAML reads it as a nested key. Synapse says so by name rather than passing the raw parser error through. undefined Synapse ships one skill, synapse-mesh , covering how to actually run a team of agents. It lands in your library on first use and is an ordinary editable skill from then on. Installing shell Copy synapse skill list # what is in the library synapse skill create my-workflow # start one from a template synapse skill edit my-workflow # open it in $EDITOR synapse skill install # copy everything into every tool synapse skill install my-workflow # or just one synapse skill install --tool claude # or into just one tool synapse skill status # where each one is synapse skill remove my-workflow # take it back out Editing a skill in the library leaves the installed copies behind, and status says so. Running install again brings them back in step. In the app, the Skills screen does the same thing with a button per skill and one that installs everything. What each state means State What it means What Synapse will do not installed The tool does not have this skill. Install it. installed The copy matches the library. Nothing needed. update available Synapse installed it, and the library has changed since. Install again to sync. changed in place Synapse installed it, and it has been edited inside the tool. Refuse, unless you pass --replace . not ours A skill of the same name that Synapse never wrote. Refuse. It is yours, not Synapse's. not in the library The tool has a skill Synapse does not know about. Nothing, until you adopt it. waiting for review An agent wrote it and you have not looked at it yet. Nothing, until you approve it. Synapse knows which copies are its own because it records the digest it wrote and the library digest it came from. Without that record, \"the library moved on\" and \"somebody wrote this by hand\" look identical, and the safe response to both would be to do nothing. Adopting what you already have If a tool already has a skill you want managed, adopt it. That copies it into the library and records the tool it came from as already having it, so the original stops reading as somebody else's and starts staying in step. shell Copy synapse skill adopt humanize --tool claude synapse skill install humanize The app lists these under Already in your tools on the Skills screen, with an Adopt button beside each. Finding published skills Other people publish skills too. synapse skill search asks skills.sh , a public index of the skills on GitHub that weighs a match by how many people installed it, and synapse skill get brings one into your library. shell Copy synapse skill search release notes synapse skill get phuryn/pm-skills/release-notes synapse skill show release-notes synapse skill approve release-notes A skill you get arrives proposed , exactly like one an agent wrote: it is in the library and in no tool until you approve it. It is somebody else's instructions, its description would be loaded into every session of every tool holding it, and a skill can carry scripts, so read it first. Every file is checked before anything is written — paths stay inside the skill, the frontmatter is valid, and a skill already in your library is never replaced. skills.sh is one source among any you add: a team's own GitHub repository, or another index. Every source is asked at once, and results are written source:id . See Sources . The app's Skills screen has the same thing as a second tab: Search lists the results down the left and shows the selected skill's whole SKILL.md beside them, with Add to library . Searching is the one part of the skill library that reaches the network, and only when you search. The query and the skill you fetch go to skills.sh; nothing else about your machine or library does. undefined Project skills A procedure you work out in one repository is usually about that repository. A library where every skill is global costs every session on the machine to hold one project's checklist, so skills have shelves : the library root for what is true everywhere, and one shelf per project beside it. shell Copy synapse skill create release --project # this repository's own synapse skill install release --project # into this repository's .claude/skills synapse skill list --global # or just the shared ones A project skill installs into that project's own skills folders rather than into your home, so it is loaded when you work there and nowhere else. It also travels with the repository once it is installed, which is the point. Every command that takes a bare name looks at this project's shelf before the global one, so synapse skill show release run inside a repository shows that repository's version. A global skill and a project skill can share a name without colliding; --global and --project [folder] say which you mean when it matters. Not every tool has a place for these A project skill needs a project-local skills folder, and a tool described without one simply has nowhere to put it. Synapse leaves that pairing out of skill status rather than reporting it as something to fix. Skills agents write Switch this on and a session can write down a procedure it worked out, and correct one that turned out wrong: shell Copy synapse settings learn on That adds two tools, teach and revise , to every connected session, and the guidance explaining them arrives with them. It is off until you ask for it, because a tool definition costs context in every session that loads it. Synapse has no model and runs no loop, so it never reflects on a session. The session decides what it learned; Synapse decides where that lands and who has to agree to it. Nothing reaches a tool until you say so A skill an agent writes is proposed . It is in the library and in no tool, and it stays there until you approve it. The gate is on installing rather than on writing, because the library is Synapse's own folder while a skill's description is loaded into every session of every tool holding it — so writing one costs you a line in a list, and installing it is the decision. shell Copy synapse skill proposed # what is waiting, and who wrote it synapse skill show cut-a-release # read it synapse skill approve cut-a-release # install it where it belongs synapse skill reject cut-a-release --confirm synapse skill install with no name steps over anything waiting, so a proposal never reaches a tool because you installed everything. The app shows the same queue on the Skills screen with Approve and Turn down beside each. You will also see it said once at the start of a session — Synapse connected · 1 skill to review — and nowhere else. Waiting is what a proposal is for; a queue that interrupts is not a queue. Corrections do reach the copies revise is the deliberate exception. You already agreed to that skill being loaded, and a correction that never arrives leaves every session running the version that was wrong — so a revision goes out to the copies Synapse installed. It reaches only copies Synapse wrote that nobody has edited since; one you changed by hand is yours and is left where it is. Nothing is lost doing it. What the skill said before is kept, and putting it back is one command: shell Copy synapse skill history cut-a-release # what it used to say, and what was wrong synapse skill revert cut-a-release # put the last version back Reverting is itself a revision, so a revert can be reverted. The newest twenty versions of each skill are kept. Limits Installing copies the directory rather than linking it, so a copy stays put if the library moves. The cost is that an edit in the library has to be installed again to reach the tools, which is what status is for. Deleting a skill from the library does not remove the copies already installed. Use synapse skill remove for that, before or after. A skill whose SKILL.md does not parse is reported and skipped rather than copied anywhere. A skill an agent writes is only ever a skill. It cannot install itself, cannot reach a tool, and cannot write anywhere except the library."},{"title":"MCP servers","description":"Keep one list of MCP servers, give it to every connected tool, and keep their tokens in the vault rather than in each tool's config.","path":"docs/servers/","text":"Why a library Every coding tool keeps its own list of MCP servers in its own format, so the same server ends up configured three times, and its token sits in plaintext in three files. Synapse keeps one list instead. Each server is a small TOML file in the Synapse data directory, and its secrets are held as references into the vault rather than as values. What a tool is given is not the server itself. It is synapse mcp run <name> , written through that tool's own mcp add . When the tool starts it, Synapse reads the library, fetches the secrets from the vault, and replaces itself with the real server. Nothing is proxied: the tool talks to the server directly and keeps everything its own client does, and Synapse is gone from the path the moment the server starts. Editing a server needs no reinstall. Tools only ever hold the <code>run</code> line, so the next session that starts the server picks up the change. undefined Adding a server Describe it once. Everything after the bare -- is the command, so its own flags are never read as Synapse's: shell Copy synapse mcp add github -- docker run -i --rm -e GITHUB_PERSONAL_ACCESS_TOKEN ghcr.io/github/github-mcp-server synapse mcp show github synapse mcp edit github The file it writes has a command , its args , plain settings under [env] , and secrets under [secrets] . A value can only be written under [env] , which is what keeps a token out of it: the [secrets] table holds nothing but references. toml Copy command = "docker" args = ["run", "-i", "--rm", "-e", "GITHUB_PERSONAL_ACCESS_TOKEN", "ghcr.io/github/github-mcp-server"] [secrets] GITHUB_PERSONAL_ACCESS_TOKEN = "mcp-github.github-personal-access-token" Secrets A secret is entered at a prompt or piped in, never passed as an argument, the same as every other secret Synapse holds: shell Copy synapse mcp secret github GITHUB_PERSONAL_ACCESS_TOKEN Each server gets a vault of its own, named mcp-<server> , because a vault holds one secret per environment variable and two servers wanting GITHUB_TOKEN is ordinary. In the app, a server that still needs a secret shows a Set button beside it, and the value goes from the field straight into the vault. A server is not started while anything is missing — a secret with no value, a setting left empty, or an argument still written as a <placeholder> . It is refused with a line saying what to fill in, rather than starting half-configured and failing somewhere less obvious. Installing shell Copy synapse mcp install # every server, every tool that can hold one synapse mcp install github --tool codex synapse mcp status synapse mcp uninstall github Claude Code, Codex, and Ainz can each be given library servers. pi has no MCP client of its own, so it gets no row. A tool somebody describes can take them too by adding a [servers] section to its descriptor with the add , remove , and at templates. What each state means State Meaning installed The tool starts this server through Synapse, from this binary. update available Through Synapse, but from a binary that has moved. install rewrites it. not ours The tool already has its own server by this name. It is left alone unless you say to replace it. not installed The tool does not have it. Whether an entry is Synapse's is read from the entry itself — synapse mcp run <its own name> — so there is no record to drift from what the file says. synapse disconnect and synapse uninstall take every such entry back out; a reset does not, because fixing a connection is no reason to lose a tool's servers. Adopting what you already have A server a tool already has can move into the library. Every value its entry carries goes into the vault — all of them, not only the ones whose names look secret, because a value misjudged as harmless would sit in plaintext in the library. shell Copy synapse mcp adopt github --tool claude synapse mcp install github --tool claude --replace Adopting does not touch the tool. Its own entry still holds the token until you install over it with --replace , which swaps it for the run line and takes the plaintext out. In the app, that is the Swap button, offered only for the tool the server was adopted from. Finding servers synapse mcp search asks the official MCP Registry , and synapse mcp get turns an entry into a library server: an npm package becomes npx , a PyPI one uvx , a container image docker run . Whatever the entry needs and gives no value for is left for you to fill in. shell Copy synapse mcp search postgres synapse mcp get io.github.owner/server-name synapse mcp install server-name The official registry is one source among any you add: an organisation's own registry, or a GitHub repository holding server.json files. See Sources . The registry is slow — fifteen to forty-five seconds a search when measured — and sorts by name, with nothing like a download count to rank by. Synapse puts what it can run first and waits a full minute before giving up. undefined Limits Remote servers, which a tool reaches over HTTP rather than starting, are not kept here. Add those in each tool directly. A server's secrets are handed to it whenever a tool starts it, from any folder. The mapping you set up is the permission; there is no per-project scope for library servers. Deleting a server with synapse mcp delete <name> --confirm takes it out of every tool Synapse gave it to and forgets its vault. A tool's own entry by the same name is left alone."},{"title":"Sources","description":"Choose where skill and MCP searches look: public indexes, MCP registries, and your own or your team's GitHub repositories.","path":"docs/sources/","text":"What a source is synapse skill search and synapse mcp search , and the Search tabs in the app, ask every source at once and label each result with the one it came from. A source is one of three kinds: Kind Holds What it is skills index Skills skills.sh, or anything answering its /api/search and /api/download . MCP registry MCP servers The official MCP Registry, or any registry implementing its /v0/servers API — including one an organisation runs for itself. GitHub repository Both Every SKILL.md folder and every server.json in a repository, or in one folder of it. skills.sh and the official MCP Registry are built in. Once you change the list it is yours, so a built-in can be removed like any other. Adding one shell Copy synapse source add team acme/agent-skills synapse source add team acme/agents@main/skills # a branch and a folder synapse source add internal https://mcp.example.com # a registry synapse source list synapse source remove skills.sh Synapse works out the kind. owner/repo or a github.com address is a repository; an address is asked whether it answers as an MCP registry or as a skills index, so a typo is caught when the source is added rather than on every search. --kind settles it when asking cannot. Results are written source:id , which is what get takes. A bare id still means the first index, so synapse skill get owner/repo/skill works as it always has. shell Copy synapse skill search release notes synapse skill get team:skills/release-notes synapse mcp get internal:com.example/billing In the app, the bottom of each Search tab lists the sources that tab asks, with a field to add one and Remove beside each. Private repositories A private repository reads its token from the vault, and the sources file holds only the reference: shell Copy synapse secret set work github GITHUB_TOKEN # prompted synapse source add team acme/agent-skills --token work.github A project's sources A repository can carry .synapse/sources.toml so everyone working in it searches the team's own sources: toml Copy [[source]] name = "team" kind = "github" repo = "acme/agent-skills" path = "skills" It is used only once somebody has approved its exact contents, the same bargain .synapse.yaml makes. A file somebody else committed decides where your searches go and whose instructions come back, so it is listed and not searched until you say otherwise: shell Copy synapse source allow Editing the file takes the approval away until it is allowed again. A project source whose name is already in your own list is skipped and reported, so team:release always means one thing. Approving a source is not approving what it holds. A skill from any source still arrives waiting for review, and a server still reaches no tool until you install it. undefined How a repository is read One request to GitHub's trees API lists the whole repository, and the files come from raw.githubusercontent.com, which does not count against GitHub's allowance of sixty requests an hour without a token. The catalogue — every skill's name and description, every server.json — is cached in the data folder for an hour, and a refresh whose tree has not changed reads nothing else. synapse source refresh reads it again now. A server.json straight out of a repository is often the template a release fills in, with ${VERSION} where the version goes. Synapse cannot fill it, so the server runs unpinned."},{"title":"Vaults and scopes","description":"Keep values in an encrypted store or macOS Keychain, map names through approved YAML, understand precedence, and choose a command-scoped or ambient environment.","path":"docs/vault/","text":"Storage model A vault is an organizational name. A secret record connects a vault-local label to an environment-variable name and an account reference. brain.db stores that metadata and never a value; the value itself goes to whichever store this machine keeps values in. A reference uses vault.name form, such as work.database . Scope YAML maps an environment name such as DATABASE_URL to that reference. A repository can therefore contain the mapping without containing the value. Object Example Rules Vault work Letters, numbers, and hyphens; unique name. Secret name database Letters, numbers, and hyphens; unique inside its vault. Environment name DATABASE_URL Starts with a letter or underscore; then letters, numbers, or underscores; stored uppercase. Reference work.database Vault and secret name joined by a dot. Where values live Synapse has two value stores, and the choice is yours rather than the platform's. Store What it is What it protects encrypted vault.db in the data folder, one XChaCha20-Poly1305 envelope per secret, sealed with a 32-byte key in vault.key . Both files are owner-only. A vault that has been copied — a backup, a synced folder, a disk image, a machine somebody else now has. keychain macOS Keychain, one generic password per secret under the app.synapse.vault service. The same, plus per-application access control enforced by macOS. The encrypted store is the default on a new installation and the only one available off macOS. A machine that was already holding secrets before this release stays on Keychain until you move it, so an upgrade never stops resolving a credential. shell Copy synapse vault backend synapse vault migrate keychain synapse vault migrate encrypted --keep vault backend with no argument prints the current store. With an argument it only records a choice, and refuses once secrets exist — moving is vault migrate , which copies every value, reads each one back, switches the setting, and then removes the originals. --keep leaves the originals where they are. A store that cannot be read stops the migration before anything switches, so a Keychain prompt you decline cannot strand your values. Keychain is the stronger bargain on a Mac It gates access per application, where a key file protects a vault that has been copied and not a process already running as you. It is a setting away in both directions, and neither store ever holds a value in plaintext. Create a secret shell Copy synapse vault create work synapse secret set work database DATABASE_URL synapse secret list work When stdin is a terminal, secret set reads the value with a hidden prompt and asks for confirmation. When stdin is piped, it reads the stream. The value is never accepted as a command argument. Surrounding whitespace is removed before storing, and secret set says so when it removed any: a pasted trailing space is invisible in a terminal and turns into an authentication failure indistinguishable from a wrong password. A value equal to the vault, the secret name, or the environment name is refused outright and nothing is stored — three name-shaped arguments stand in front of the one that is not a name, and this is what happens when the value lands one slot late. What was stored is then echoed back as a shape, never a value: text Copy Saved work.database in the encrypted vault · 19 chars · ••••-••••-••••-•••• The length and the punctuation are enough to recognise the credential you meant to enter, or to see that it went in one character longer than what you copied. The desktop app applies the same guards and prints the same shape. shell Copy printf '%s' "$DATABASE_URL" | synapse secret set work database DATABASE_URL Shell history boundary The safe prompt keeps the value out of history and process arguments. A piped command is only as safe as the command that produces its stdin; do not paste a value directly into a visible shell command. Add --global to make the environment name available in every folder, or change an existing label later: shell Copy synapse secret set work registry NPM_TOKEN --global synapse secret global work.registry off Get a value back A value never appears on screen, in a log, or in an MCP response. The one way to retrieve one is onto the clipboard: shell Copy synapse secret copy work.database The command prints the reference it copied and never the value. In the desktop app the same thing is a Copy button on the secret's row. On macOS the value goes through pbcopy over stdin, never as a command argument; set SYNAPSE_CLIPBOARD to name a different command. Scope files From a project folder, create .synapse.yaml : shell Copy synapse scope init . The generated project template is valid but maps nothing: yaml Copy version: 1 scope: project env: {} deny: [] Edit it to map names to existing references. Unknown fields are rejected. yaml Copy version: 1 scope: project env: DATABASE_URL: work.database NPM_TOKEN: work.registry deny: - PRODUCTION_TOKEN Use synapse scope init path/to/folder --folder for a nested folder scope. The scope field describes intent and appears in status output; resolution uses the file’s position in the ancestor path. Approval A scope has no effect until its exact contents are approved. Synapse hashes the file and stores its canonical path plus digest in SQLite: shell Copy synapse status . synapse allow synapse scope status . synapse allow approves the closest discovered scope. If several ancestor scopes exist, approve each from its own folder. synapse scope trust [folder] remains available when you want to approve an exact path directly. Changing even whitespace changes the digest. The scope then reports changed and stops applying until you inspect and approve it again. Invalid YAML and unknown references produce warnings. Both shell modes refuse an incomplete environment when any discovered scope is pending, changed, invalid, or references an unknown secret. Command-scoped mode refuses to launch; ambient mode unloads its managed values. Precedence and deny Global mappings load first. Synapse discovers every .synapse.yaml from the filesystem root toward the working folder. Approved files apply in that order, so the closest mapping replaces a broader mapping for the same environment name. A name in deny is removed and remains denied for all narrower scopes. A child scope cannot add it back. This lets a repository override a harmless global credential while permanently blocking a production credential inside a sensitive subtree. Choose an environment boundary Command scoped shell Copy cd /path/to/project synapse status . synapse run -- cargo test synapse run -- bun run deploy synapse run resolves the current working folder, verifies every discovered scope, reads each selected value out of the vault, and sets it only on the new child process. Normal environment inheritance still applies; Synapse adds or replaces the resolved names. Your current shell is unchanged. Ambient directory Open Settings → Shell environments and choose Enable shell hook . Synapse detects the default shell, installs the CLI if needed, and manages a marked startup-file block. Open a new terminal, then allow the project: shell Copy cd /path/to/project synapse allow For a temporary or manual installation, evaluate the matching hook directly: shell Copy # Add the matching line to your shell startup file. eval "$(synapse hook zsh)" # eval "$(synapse hook bash)" # synapse hook fish | source The hook loads the approved environment when the shell enters the project, unloads it when the shell leaves, and reloads it after an approved change. If Synapse replaced a variable that already existed, it restores the original value instead of unsetting it. A hook never activates from global mappings alone: the directory must contain at least one approved discovered scope. Run synapse deny to revoke the closest scope. The next prompt unloads managed values. synapse status . reports whether ambient activation is ready, blocked, or inactive and whether the current shell has a hook installed. Ambient mode has a wider boundary Every process launched from the activated shell can read the values. Prefer synapse run for a sensitive one-off command. Do not enable shell tracing such as set -x while the hook is evaluating environment changes, because tracing can print plaintext values. MCP vaultstatus and CLI status report names, scope states, and warnings. They do not read or reveal values. To remove a value and its metadata, run synapse secret forget work.database . A vault can be deleted only after all of its secrets are forgotten."},{"title":"Data lifecycle","description":"Locate the database, understand startup checks and automatic backups, export a consistent snapshot, and restore safely.","path":"docs/data/","text":"Files and permissions On macOS, the default data directory is ~/Library/Application Support/synapse . Run synapse path to print the resolved data, shared-guidance, and CLI paths for the current environment. Path Purpose brain.db Scoped memory, import provenance and batches, settings, vault metadata, global mappings, and scope approvals. SOUL.md The editable shared guidance loaded by both connected tools and the MCP server. brain.db-wal and brain.db-shm SQLite write-ahead-log sidecars while the database is active. brain.lock Shared lifecycle lock held by the app, CLI operations, and MCP server. backups/ Automatic pre-migration and pre-restore SQLite snapshots. On Unix systems the data and backup directories use mode 0700 ; database files, sidecars, locks, and backups use 0600 . Startup Every database open follows the same sequence: Create and secure the data directory if needed. Acquire a shared lifecycle lock. Open SQLite with foreign keys enabled, a five-second busy timeout, and WAL journal mode. Run PRAGMA foreign_key_check , and PRAGMA quick_check when this store has not already been read. Reject a database newer than the current application supports. Create a pre-migration snapshot when an existing database needs a numbered migration, then apply the migration transactionally. Move project memory filed under a repository's folder path to that repository's origin key, when the folder is still a repository with a remote. A store with nothing to move costs one indexed read. Reapply owner-only file permissions. <code>quick_check</code> reads every page, so its cost grows with everything you have stored. Synapse runs it once for a given file rather than once per handle, and reporting commands such as the status line skip it: they redraw constantly, and a whole-store scan there would cost far more than the number they print. <code>synapse data check</code> always runs the full check. undefined shell Copy synapse data check synapse data check --json Automatic backups Before changing an existing schema, Synapse creates backups/brain.<timestamp>.v<version>.db . Before replacing an existing database during restore, it creates backups/brain.<timestamp>.restore.db . Upgrading to schema 13 rebuilds the search index: memory is stemmed, sources become searchable, and a fragment index is added beside it. Like every migration it is one-way. An earlier Synapse refuses a store this release has opened, and says so. To go back, close Synapse and restore the .v12.db snapshot it took first. These are complete snapshots created through SQLite VACUUM INTO , not copies of a live database file. Synapse does not currently rotate them; include the folder in your normal local backup policy and remove old snapshots deliberately. Export a portable snapshot shell Copy synapse data export "$HOME/Desktop/synapsebackup.db" synapse data check --json The destination must not already exist. Synapse opens the active database normally, writes a consistent compact snapshot, secures its permissions, then reopens it read-only and validates integrity before reporting success. Secret values are separate The export covers brain.db: memory, vault metadata, and account references, and never a value. Values live in their own store — vault.db beside it, or macOS Keychain — and neither is in the snapshot. Restoring on another machine restores the labels; the values are set again there, or vault.db and vault.key are carried across together and treated like the secret they are. Restore a snapshot Quit the Synapse desktop app. Stop every connected MCP process by closing the relevant tool sessions. Run the restore command from a separate terminal. shell Copy synapse data restore "$HOME/Desktop/synapsebackup.db" synapse data check Restore opens the source read-only, validates page and relationship integrity, and requires its schema version to match the current release. It then tries to acquire the exclusive lifecycle lock. If the app or any MCP server still holds the database, restore refuses without changing anything. When a current database exists, Synapse validates it and creates a recovery snapshot before atomically replacing it. WAL and SHM sidecars are cleared, file permissions are secured, and the containing directory is synced. Memory wipe is not a reset synapse memory wipe --confirm removes every memory entry and import batch but leaves SOUL.md , settings, vault labels, stored values, global mappings, and scope approvals. Use it when you want a clean memory history without rebuilding guidance or credential setup. There is no one-command factory reset. To remove Synapse completely, first forget each secret through the app or CLI, then quit all processes and remove the application, installed launcher, and data directory deliberately. On the encrypted store the data directory is the credential store, so removing it takes every value with it — synapse uninstall --data says so before it acts."},{"title":"Configuration and paths","description":"Find every Synapse-owned path, understand environment overrides, and know how integration and editor writes are protected.","path":"docs/config/","text":"Resolved paths shell Copy synapse path Item Default on macOS Home ~ Data directory ~/Library/Application Support/synapse Database ~/Library/Application Support/synapse/brain.db Shared guidance ~/Library/Application Support/synapse/SOUL.md Skill library ~/Library/Application Support/synapse/skills/ Mesh roles and teams ~/Library/Application Support/synapse/roles/ , teams/ Worker logs ~/Library/Application Support/synapse/workers/ Database backups ~/Library/Application Support/synapse/backups/ Installed CLI ~/.local/bin/synapse Everything above lives under one folder. A project may also hold .synapse.yaml scope files and a .synapse/roles/ directory, both of which travel with the checkout rather than with you. Environment variables Name Effect Typical use SYNAPSE_DATA Replaces the application-data directory. Development, tests, or a deliberately isolated database. SYNAPSE_HOME Replaces the home directory used for tool files and the default CLI path. Integration testing without touching real user files. SYNAPSE_BIN Replaces the full CLI installation destination. Install outside ~/.local/bin . SYNAPSE_PROJECT_DIR Provides the fallback project folder for MCP calls that do not carry one, and is what a launched agent is told to treat as home. A client that launches the server outside the project working directory. Set automatically by synapse launch and relay launch . SYNAPSE_PAGE Chooses which dashboard page opens first: map , memory , mesh , skills , vaults , or settings . Anything else opens Connections. Opening the app straight to the screen you want. CODEX_HOME Replaces the Codex home directory used for detection and for reading an existing Codex memory store. A non-default Codex installation, or testing an import without touching real files. PI_CODING_AGENT_DIR pi's own variable for its agent directory, which Synapse reads for the same reason: detection, the instruction pointer, and the skills folder. A non-default pi installation. SYNAPSE_PI_PACKAGE Replaces the package a connection to pi installs, and the source detection compares against. Defaults to npm:synapse-pi . Working on the package from a checkout instead of npm. SYNAPSE_COMMAND The Synapse executable a launched pi session should talk to. Set automatically by synapse launch pi so a session reaches the binary that started it. SYNAPSE_SHELL_ACTIVE Identifies the shell hook currently evaluated in this shell. Set automatically by the zsh, bash, or fish integration so status can report it. SYNAPSE_SHELL_KEYS Records which managed variables the hook currently has loaded, so leaving a scope unloads exactly those and restores what was there before. Set automatically by the shell hook. Not intended to be set by hand. SYNAPSE_SHELL_COMMAND The executable the installed shell block re-invokes on each directory change. Set automatically by the startup-file block so a moved app is still reachable. SYNAPSE_DOCUMENT Launches the desktop app directly into a Markdown, TOML, JSON, or YAML document. Internal editor entrypoint. shell Copy SYNAPSE_DATA="$HOME/tmp/synapsedemo" synapse data check SYNAPSE_BIN="$HOME/bin/synapse" synapse install Overrides affect only the process that receives them. Keep the desktop app, CLI, and connected MCP server on the same SYNAPSE_DATA value if they should share one store. Tool integration files Custom harness definitions live in .synapse/tools/<name>.toml for a project, or tools/ in the Synapse data directory for a user. Create one with synapse tool create <name> , adding --user for the user layer. Project definitions override user definitions, which override built-ins. See the custom harness guide for the schema and a working example. .synapse.yaml remains the credential-scope configuration. Tool Integration Instructions Settings shortcut Codex ~/.codex/config.toml ~/.codex/AGENTS.md ~/.codex/config.toml Claude Code ~/.claude.json ~/.claude/CLAUDE.md ~/.claude/settings.json pi ~/.pi/agent/settings.json ~/.pi/agent/APPEND_SYSTEM.md ~/.pi/agent/settings.json Ainz ~/Library/Application Support/ainz/mcp.toml ~/Library/Application Support/ainz/AGENTS.md ~/Library/Application Support/ainz/config.toml Connection detection parses the actual TOML or JSON entry named synapse . It reports connected only when the stored command resolves to the expected executable and the arguments equal [\"mcp\"] . A deleted development binary therefore appears stale instead of healthy. pi is the exception, because its connection is a package rather than a server command: detection reads pi's packages list and reports connected when the Synapse package is there. The extension in that package starts the same synapse mcp server and finds the executable itself, so there is no stored path to go stale. Each instruction file contains a managed pointer to the central SOUL.md . A normal sync preserves other text in place. The optional consolidation action first moves that text into SOUL.md , then leaves the global files pointer-only so shared guidance has one editable source. Connecting Claude Code also writes two entries into ~/.claude/settings.json : a SessionStart hook running synapse session , and a statusLine running synapse statusline . JSON has no comment syntax, so Synapse cannot mark its own entries the way it marks a block in a Markdown file. It recognizes them by the command they run instead, and carries everything else through untouched — a status line you configured yourself is reported and left alone, never replaced. Entry Command What it produces hooks.SessionStart synapse session The connection line in your terminal, and this project's memory in the session's context before the first turn. See Session start . statusLine synapse statusline One line under the prompt for the rest of the session: model, folder, memory count, and mesh size. Both are removed by synapse disconnect claude . Codex and Ainz expose neither, so those connections are the MCP entry and the instruction pointer only. pi has no settings entries of this kind either — its notice and status line come from the extension in its package rather than from a hook it has to be configured with. Safe writes The built-in document views validate JSON, TOML, and YAML before saving. A write with changed content: Resolves a dotfile symlink and writes through it rather than replacing the link. Writes the previous bytes to a sibling file ending in .synapsebackup . Writes the replacement to a temporary file, syncs it, preserves existing permissions, then renames it atomically. Syncs the containing directory. Tool setup snapshots its integration file, instruction file, and shared guidance file. If registration or the instruction update fails, it restores every prior state. The CLI installer applies the same rollback model to its launcher and receipt. Settings and shell integration shell Copy synapse settings show synapse settings optimize full synapse settings optimize balanced synapse settings optimize lean # Manual alternative to Settings → Enable shell hook. eval "$(synapse hook zsh)" synapse allow synapse deny The optimization value lives in SQLite, so the app, CLI, and every connected MCP process read the same setting. Balanced is the default. See Memory and recall for exact limits and transformations. The Settings screen detects the default shell and can enable, repair, or remove its managed startup-file block. Existing files are backed up and replaced atomically. The hook is an explicit per-shell opt-in: it activates only inside a directory with at least one discovered, approved scope. Leaving the scope, revoking approval, or changing the YAML unloads managed values and restores values that existed before activation. See Choose an environment boundary before enabling ambient mode."},{"title":"Security model","description":"Understand the trust boundary around local memory, stored secret values, MCP metadata, approved scopes, child processes, and configuration writes.","path":"docs/security/","text":"Trust boundary Synapse is local-first, not a sandbox. It removes unnecessary network and file exposure from the memory-and-credential workflow, but it still runs as your macOS user. Any process already able to control your account, inspect your terminal, read your home directory, or access an unlocked Keychain may sit inside the same trust boundary. The application requires no Synapse account or hosted service. MCP uses local stdio. Memory and metadata live in a local SQLite file. Synapse includes no telemetry, no remote synchronization, and no web server. Memory Memory bodies are plain text in SQLite and are available to connected MCP clients through recall. Global records are available in every project; project records are returned only with the matching project, which is the repository's origin remote when it has one and its root folder otherwise. A remote written with credentials, such as https://user:token@host/… , is stored without them. Do not store credentials, private keys, access tokens, or material you would not want an authorized connected tool to read. As a backstop, every memory written on this machine is redacted before it is stored, whether it comes from a tool, the CLI, the desktop editor, or an import: values assigned to secret-sounding names, passwords in URLs, bearer tokens, tokens with a known provider prefix, and PEM private keys become [redacted] , and <private> sections are removed. Redaction recognizes shapes; it cannot know that an arbitrary string is a secret, so it is a safety net, not permission to paste credentials. Import reads recognized durable-memory stores only. It does not inspect conversation logs, authentication files, settings, tasks, or global instructions. Credential-shaped entries are flagged, hidden in previews, and skipped by the app. Provider formats are schema-checked before use, original files remain untouched, and import batches can be undone. Database files use owner-only permissions on Unix systems and are checked for page and foreign-key integrity before use. These controls protect against accidental broad access and corruption; they do not encrypt memory at rest. Secret values A secret value is never in brain.db , never in YAML, never in a log, never in an MCP response, and never at rest in plaintext. brain.db stores the vault, label, environment name, and account reference. The value goes to one of two stores. The encrypted store is the default, and the only one available off macOS. Each value is sealed with XChaCha20-Poly1305 under a random 24-byte nonce, with its account reference authenticated as associated data, and written to vault.db in the data folder. The 32-byte key lives beside it in vault.key , created once with owner-only permissions and never overwritten. Deleted rows are zeroed rather than left on a free page. Be clear about what that protects. It protects a vault that has been copied — a backup, a synced folder, a disk image, a laptop somebody else now has. It does not protect you from a process already running as your user, which can read vault.key exactly the way Synapse reads it. This is encryption at rest, not an access control. macOS Keychain is the other store, and on a Mac it is the stronger of the two: it adds per-application access control that the operating system enforces, which a key file cannot. It is one setting away in either direction, and synapse vault migrate moves every value across without a gap. secret set does not accept a value argument. Interactive input uses a hidden prompt; piped input remains available for automation. secret copy is the only way a stored value comes back out, and it goes to the clipboard rather than to a screen, handed to pbcopy over stdin rather than as an argument that would be readable in ps . Anything on the clipboard is readable by every program that can ask for it, and Synapse does not clear it for you. Metadata can still be sensitive Names such as PRODUCTION_DATABASE_URL can reveal system structure even when the value is hidden. Choose scope files and variable names with the same care you apply to ordinary repository configuration. Scope approval An unapproved, edited, or invalid .synapse.yaml never contributes variables. Approval stores a SHA-256 digest of the exact file bytes and its canonical path. Any later edit invalidates that digest. Resolution walks from broad ancestor folders toward the working folder. Narrower approved files can replace a mapping, but a name denied by a broader scope remains denied. Any scope warning prevents synapse run from launching and makes the shell hook unload its managed values, avoiding silent partial configuration. Environment boundaries synapse run -- <command> reads only the selected values out of the vault and places them in the environment of a new child process. That command and all of its descendants can read those values normally. Synapse cannot stop the child from logging, transmitting, or persisting them. The optional zsh, bash, or fish hook asks the shell to evaluate quoted environment changes. It activates only inside a directory with at least one approved discovered scope. Leaving, revoking approval, or changing any discovered scope unloads managed variables and restores values that existed before activation. Settings installs the hook inside a marked startup-file block using the absolute managed CLI path. It backs up and atomically replaces an existing file, refuses incomplete or duplicate Synapse blocks, and removes only the marked block when disabled. Ambient access is broader Every process launched from an activated shell inherits the values, including commands that do not need them. Use command-scoped mode for a smaller boundary, and do not enable shell tracing while environment changes are evaluated. Inspect scripts before launching them with production credentials. Use folder scopes and deny to narrow access. Prefer separate least-privilege credentials for development and deployment. Use synapse status . immediately before a sensitive command. Use synapse deny when a directory should no longer activate automatically. Operational limits Synapse does not isolate a malicious connected MCP client from recalled memory. Only connect tools you trust. Both stores protect a value at rest, but an authorized local child receives plaintext environment variables. On the encrypted store, the key sits beside the sealed values in the data folder. Anything running as you can open the vault; a copy of the folder taken without the key cannot. Database exports contain brain.db only — memory and credential metadata, never a value. vault.db is a separate file for exactly that reason, and it is not in the export. Deleting the data folder deletes vault.db and vault.key with it, which is every stored value. On the Keychain store the values survive it. synapse uninstall says so before it does anything. SOUL.md is ordinary local Markdown. Both connected tools can read it, so do not put secrets there. Configuration writes are validated, backed up, and atomic, but external programs can still change the same files afterward. A scope digest proves that you approved exact bytes at a path; it does not prove the commands launched from that folder are safe. There is no remote revocation system. Forget a secret in Synapse and rotate it at its issuer when exposure is possible."},{"title":"CLI reference","description":"The complete Synapse command surface, including inputs, output modes, confirmation guards, and process behavior.","path":"docs/cli/","text":"Invocation Run synapse help for the built-in summary. Every command answers --help and -h with its own entries — synapse vault --help , or synapse secret set --help for one line of it — and synapse help <command> is the same answer typed the other way round. After a -- the flag belongs to the child, so synapse run -- mytool --help asks mytool . Commands print human-readable output unless a documented --json option is present. Errors go to stderr and return a non-zero exit status. Secret and memory input Secret values are never accepted as arguments. Secret set uses a hidden prompt or stdin. Memory add and edit always read their body from stdin. Application and server app synapse app Open the native desktop application. Running synapse mcp synapse mcp Run the MCP stdio server until the client closes the connection. The process holds a shared database lifecycle lock. run synapse run -- <command> [arguments] Resolve global, project, and folder vault mappings for the current directory, refuse on any scope warning, read the selected values out of the vault, and launch the child. Synapse returns the child exit code, or 1 when the operating system reports no code. launch synapse launch &lt;claude|codex|pi&gt; [options] [-- &lt;flags&gt;] Start a coding tool with Synapse already in place: memory and the vault reachable over MCP, this folder's vault variables in its environment, and the project root it should treat as home. Everything after a bare -- is passed to the tool untouched. A tool Synapse is not connected to is wired for the life of the process, so this works before setup has ever run and writes nothing into the tool's own configuration. Options: --directory , --model , --allow-tool (repeatable), --strict , --no-vault , --skip-permissions , --as <name> with --role , --task , and --channel to join the mesh as well, and --print to show the resolved command and environment names without running anything. Refuses on any scope warning, and never prints a secret value. status synapse status [folder] [--json] Show the resolved folder, available environment names, discovered scope states, warnings, ambient readiness, and detected shell hook. Reports which value store this machine uses and never a value. The folder defaults to the current directory. Shell environments hook synapse hook <zsh|bash|fish> Print the integration script for a shell. Evaluate or source it from that shell’s startup file. The hook reevaluates approved directory scopes after directory changes and before prompts. allow synapse allow [folder] Inspect and approve the exact contents of the closest .synapse.yaml at or above the folder. The folder defaults to the current directory. deny synapse deny [folder] Revoke approval for the closest discovered scope. An installed hook unloads its managed values at the next prompt or directory change. export synapse export <zsh|bash|fish> Internal hook protocol. Emits a shell-quoted environment diff that the installed hook evaluates. Users normally invoke hook , allow , and deny instead. Two boundaries synapse run gives one child process the resolved environment. The shell hook exposes the environment to every process launched from an activated shell until the scope unloads. Vault and scope vault list synapse vault list List vault names alphabetically. vault create synapse vault create <name> Create a unique vault. Names may contain ASCII letters, numbers, and hyphens. vault delete synapse vault delete <name> Delete an empty vault. Forget every contained secret first. vault backend synapse vault backend [&lt;keychain|encrypted&gt;] Print which store holds this machine's secret values, or choose one. Choosing moves nothing, so it refuses once secrets exist and points at vault migrate . The encrypted store is the default on a new installation and the only one off macOS. vault migrate synapse vault migrate &lt;keychain|encrypted&gt; [--keep] Move every value into the other store: copy them all, read each one back, switch the setting, then remove the originals. --keep leaves the originals in place. A source that cannot be read stops the migration with nothing moved and nothing switched. secret list synapse secret list <vault> List each vault.name reference, environment name, and whether it is global or scoped. Never prints a value. secret set synapse secret set <vault> <name> <env> [--global] Read a value from a hidden terminal prompt or stdin and save it in the vault. Creates metadata when the label is new; replaces the stored value when the environment name matches. --global also enables the mapping. secret copy synapse secret copy <vault.name> Put one value on the clipboard. The only way a stored value comes back out, and it never reaches the terminal: the command prints the reference and nothing else. Set SYNAPSE_CLIPBOARD to use something other than pbcopy . secret forget synapse secret forget <vault.name> Delete the stored value, its metadata, and any global mapping. The value is removed from both stores, so a machine that has switched backends leaves nothing behind in the one it stopped reading. secret global synapse secret global <vault.name> <on|off> Enable or disable a secret’s global environment mapping. Enabling replaces the current global source for that environment name. scope init synapse scope init [folder] [--folder] Create a new .synapse.yaml . The folder defaults to the current directory. Use --folder for a folder-kind template; the command refuses when the file already exists. scope trust synapse scope trust [folder] Approve an exact scope path directly by parsing it, calculating its content digest, and storing that digest with its canonical path. synapse allow is the directory-oriented shortcut. scope status synapse scope status [folder] [--json] Alias the scope-oriented status flow to the same resolved output as synapse status . Memory memory list synapse memory list [query] [--json] [--explain] Search the body and source with the joined query words, by word in any form and by fragment, or list recent memory when empty. Returns up to 100 entries. Text mode prints ID, scope, source, and a compact preview, and marks any memory that has been superseded. With --explain , print what the search actually did first: the full-text expression the query became, the words it searched for, the words it dropped for matching nearly everything, and the fused score behind each hit, higher being better. memory grep synapse memory grep &lt;text&gt; [--json] Find memories containing an exact string, most recent first. Nothing is tokenized, ranked, or dropped — the characters given have to appear, in that order — which is what makes it the right tool for an identifier, a flag, a path, or a word the ranked search treats as noise. Matching is case-insensitive for ASCII, and a pattern after a bare -- is taken literally, so memory grep -- --no-verify searches for the flag. memory eval synapse memory eval &lt;cases.toml&gt; [--limit &lt;n&gt;] [--budget &lt;full|balanced|lean&gt;] [--fail-under &lt;0-1&gt;] [--json] Score recall against a file of labeled questions. Each [[case]] has a query , an optional project folder, and expect : text a useful memory contains, matched case-insensitively, or nothing when no stored memory answers it. Reports how often the answer ranked first, how often it came back at all, mean reciprocal rank, how often the answer text survived the response budget, how many memories came back beside the answer, and how many came back where none answer. --limit defaults to 5; --budget sets the response budget for the run and defaults to the configured one. --fail-under exits 1 when the found share is below the floor. memory drift synapse memory drift [folder] [--json] [--strict] Check everything a folder's sessions can see for memory that no longer holds: a file path that is gone from the checkout, two memories that say opposite things or carry different numbers, and two that say the same thing. Each finding comes with the command that settles it. Nothing is changed. --strict exits 1 when anything is found. Recall and the session hook run the same check on what they hand over. memory show synapse memory show <id> [--json] Print one exact memory with scope, project, source, and timestamp, or return structured JSON. The project is a repository key such as github.com/owner/repo , or a folder path for a project with no remote. memory add synapse memory add [source] [--global|--project <folder>] Read a non-empty body from stdin. Project scope is the default and resolves from the current folder; use global only for context that belongs everywhere. Credentials and <private> sections are removed before storing, and the command says how many were. memory edit synapse memory edit <id> [source] Read the replacement body from stdin and replace one existing memory. The optional source replaces the source label. The memory keeps its project, and the new body is redacted the same way memory add redacts. memory supersede synapse memory supersede &lt;old&gt; &lt;new&gt; Stop recalling one memory in favour of another. The old one is not deleted: it keeps its id, stays listed and readable, says what replaced it, and comes back with memory restore . Use this instead of leaving a correction to argue with what it corrects. memory restore synapse memory restore &lt;id&gt; Put a superseded memory back in circulation. Deleting the memory that replaced it does the same thing on its own. memory import synapse memory import <claude|codex|markdown> [path] [--confirm] Preview recognized durable memory without changing its source. Add --confirm to import safe entries. Credential-shaped entries remain flagged unless the CLI also receives --include-flagged after source review. memory imports synapse memory imports [--json] List import batches, their provider, stored and linked counts, and whether each batch is active or undone. memory undo synapse memory undo <batch> --confirm Remove memories created only by that import batch. Preserve manually edited memories and records linked to another origin. memory delete synapse memory delete <id> --confirm Delete one memory. The exact --confirm guard is required. memory wipe synapse memory wipe --confirm Delete every memory entry and import batch while leaving SOUL.md, settings, vaults, stored values, and scope approvals intact. The exact guard is required. Agent mesh These commands need the mesh switched on with synapse settings mesh on . See the agent mesh guide for what each part is for. relay status synapse relay status [--json] Show whether the mesh is on, how many agents are reachable, and how many background workers are running. relay agents synapse relay agents [--json] List every agent with its role, reachability, last reported work state, and the project it is working in. relay channels synapse relay channels [--json] List the channels in use and how many agents subscribe to each. relay feed synapse relay feed [--follow] [--since &lt;id&gt;] [--json] Print the messages agents have sent each other. With --follow the command keeps printing new ones until interrupted. relay launch synapse relay launch &lt;name&gt; [options] Open one agent in this terminal, wired into the mesh with a role. Options: --role , --tool claude|codex|pi , --task , --channel (repeatable), --allow-tool (repeatable), --model , --directory , --lead , --optimize , --strict , --skip-permissions , --command <template> , and --print to show the resolved command without running it. relay team open synapse relay team open &lt;name&gt; [--directory &lt;folder&gt;] Open a whole roster. The first member runs in this terminal as the lead; the rest run in the background and stop when the lead closes. mux synapse mux [--as &lt;name&gt;] [--team &lt;team&gt;] [--channel &lt;name&gt;]... [--directory &lt;folder&gt;] Join the mesh as yourself and drive a team from one terminal. You get a name on the roster and the same messaging every agent has, so you can address any agent directly instead of relaying through a lead — and an agent that gets stuck can ask you . With --team the whole roster starts in the background with you as the lead. Type @name text for one agent, #channel text for a channel, !text for everyone, or a bare line to whoever is focused. /help lists the commands; /quit leaves and stops the workers it started. The name defaults to your login name. relay role synapse relay role &lt;list|show|create|edit|delete&gt; [name] [--user] [--json] Manage reusable agent roles. Create, edit, and delete write into the project by default, or into your own layer with --user . Editing a built-in copies it down first, and a file that does not parse is never saved. relay team synapse relay team &lt;list|show|create|edit|delete&gt; [name] [--user] [--json] Manage team rosters, resolved and edited exactly like roles. relay ps synapse relay ps [--json] List background workers with their state, process id, and log path. relay kill synapse relay kill &lt;name&gt; Stop a background worker. A worker owned by a Synapse session that is still running has to be stopped from there. session synapse session [--json] Report this session's Synapse connection as Claude Code session-hook output. Synapse installs this for you when you connect Claude Code; run it by hand to see exactly what a session will be told. When the input's source is compact , it prints no notice and hands the session's memory back instead, because the compaction dropped it. Reads the calling tool's JSON on stdin. compact synapse compact Answer a tool's pre-compaction hook. Asks the session to write down anything durable it settled before its context is compressed away, and recalls nothing — the window is being reclaimed. Synapse installs this for you when you connect Claude Code. Never blocks or rewrites the compaction. Reads the calling tool's JSON on stdin. statusline synapse statusline Print one status line for a connected tool, reading that tool's JSON on stdin. Skills One Agent Skills library, installed into every connected tool. See the skills guide for the format and the folders involved. skill list synapse skill list [--json] List the library with each skill's file count and description. A skill whose SKILL.md does not parse is reported on stderr and skipped. skill show synapse skill show &lt;name&gt; Print one skill's SKILL.md . skill create synapse skill create &lt;name&gt; Start a skill from a template. Names follow the standard: lowercase letters, digits, and single inner hyphens. skill edit synapse skill edit &lt;name&gt; Open a skill in $VISUAL or $EDITOR . A draft that does not parse is never saved over the working one. skill delete synapse skill delete &lt;name&gt; --confirm Remove a skill from the library. Copies already installed in tools are left alone. skill install synapse skill install [name] [--tool &lt;tool&gt;] [--replace] Copy the library into your tools. Without a name it installs everything; without --tool it installs into every connected tool. A copy that was edited in place, or a skill Synapse never wrote, is refused unless --replace is given. skill remove synapse skill remove &lt;name&gt; [--tool &lt;tool&gt;] [--force] Take a skill back out of a tool. Only a copy Synapse installed and nobody has changed is removed; --force overrides that. skill status synapse skill status [name] [--json] Show where each skill stands in each tool, and any skill a tool has that the library does not. skill adopt synapse skill adopt &lt;name&gt; [--tool &lt;tool&gt;] Copy a skill a tool already has into the library and record that tool as having it, so it stops reading as unmanaged. Every command that takes a bare name resolves this project's shelf before the global one, so a repository's own release shadows the shared one rather than colliding with it. Pass --global or --project [folder] to say which you mean. skill proposed synapse skill proposed [--json] List the skills agents have written that nobody has looked at yet, oldest first, with the tool that wrote each one and the line it left saying why. A proposed skill is in the library and in no tool. skill approve synapse skill approve &lt;name&gt; [--tool &lt;tool&gt;] Install a proposed skill where it belongs and stop calling it proposed. A global skill goes into every connected tool's personal skills folder; a project skill goes into that project's. skill reject synapse skill reject &lt;name&gt; --confirm Turn a proposed skill down. It leaves the library with its history, and nothing else on the machine ever knew about it. Only a skill still waiting for review can be rejected — once one is approved it is an ordinary skill, and skill delete is what removes it. skill history synapse skill history &lt;name&gt; [--json] Show what a skill used to say, newest first, with the line saying what was wrong with each version. The newest twenty are kept. mcp add synapse mcp add &lt;name&gt; [--description &lt;text&gt;] -- &lt;command&gt; [args...] Describe an MCP server for the library. Everything after the bare -- is its command. Nothing reaches a tool until mcp install . mcp list synapse mcp list [--json] List the library's servers, the command each runs, and anything one still needs before it can start. mcp show synapse mcp show &lt;name&gt; Print one server's description. Secrets appear as vault references, never values. mcp edit synapse mcp edit &lt;name&gt; Edit a server in $EDITOR . A draft that is not a valid server is kept rather than saved. Tools pick the change up the next time they start it. mcp delete synapse mcp delete &lt;name&gt; --confirm Take a server out of every tool Synapse gave it to, forget its vault, and remove it from the library. A tool's own entry by that name is left alone. mcp secret synapse mcp secret &lt;name&gt; &lt;VARIABLE&gt; Store one of a server's secrets in its own vault, read from a prompt or stdin, and point the server at it. mcp install synapse mcp install [name] [--tool &lt;tool&gt;] [--replace] Give library servers to connected tools through each tool's own mcp add , as synapse mcp run <name> . A tool's own server by the same name is left alone unless --replace is given; a server that still needs something is skipped. mcp uninstall synapse mcp uninstall &lt;name&gt; [--tool &lt;tool&gt;] Take a server back out of the tools Synapse gave it to. It stays in the library. mcp status synapse mcp status [name] [--json] Show where each server stands in each tool — installed, update available, not ours, not installed — and the servers tools have that the library does not. mcp adopt synapse mcp adopt &lt;name&gt; [--tool &lt;tool&gt;] Move a tool's own server into the library, every value it carries into the vault. The tool's entry is unchanged until you install over it with --replace . mcp search synapse mcp search &lt;words&gt; [--json] Search every source that holds MCP servers — registries and repositories — with what Synapse can run first. A registry is slow; allow half a minute. mcp get synapse mcp get &lt;source:id&gt; [--name &lt;name&gt;] Add a registry entry to the library as the command that runs it — npx , uvx , or docker run — with anything it needs left to fill in. mcp run synapse mcp run &lt;name&gt; What a tool starts. Reads the library and the vault, then replaces itself with the server. Writes nothing to stdout of its own. skill search synapse skill search &lt;words&gt; [--json] Search every source that holds skills at once — skills.sh and any repository you added — and print each result as source:id with its installs or description and its page. A source that cannot answer is reported beside the rest. skill get synapse skill get &lt;source:id&gt; Bring a published skill into the library, proposed. It reaches no tool until skill approve . A bare owner/repo/skill means the first skills index. Refused if the library already has a skill by that name. source list synapse source list [--json] List where skill and MCP searches look: each source's kind, what it holds, where it is, and whether it is built in, yours, or this project's — and whether that project's file is approved. source add synapse source add &lt;name&gt; &lt;owner/repo | address&gt; [--kind &lt;kind&gt;] [--token &lt;vault.name&gt;] Add a skills index, MCP registry, or GitHub repository. The kind is worked out — a repository needs no request, an address is asked — unless --kind says. A private repository names its token in the vault. source remove synapse source remove &lt;name&gt; Stop searching a source, built-in or yours. A project's sources are edited in its .synapse/sources.toml . source allow synapse source allow Approve this project's .synapse/sources.toml as it reads now. Until then its sources are listed and not searched, and editing it takes the approval away. source refresh synapse source refresh [name] Read a repository source's catalogue again now rather than within the hour. skill revert synapse skill revert &lt;name&gt; [&lt;id&gt;] Put an earlier version back, in the library and in every tool holding a copy Synapse wrote. What it said is kept as a new revision, so a revert can itself be reverted. Data and settings data check synapse data check [--json] Open the database, run page and foreign-key integrity checks, apply supported migrations, and report the path, schema version, and ok integrity state. data export synapse data export <file> Create and validate a consistent SQLite snapshot of brain.db at a destination that does not already exist. Secret values live in a separate store — Keychain, or vault.db beside it — and are never in the snapshot. data restore synapse data restore <file> Validate a current-version snapshot and restore it while the app and MCP servers are closed. Preserves the previous database as a recovery backup and refuses without the exclusive lock. settings show synapse settings show Print the active recall optimization, result limit, character budget, supported shell modes, and zsh hook example. settings optimize synapse settings optimize <full|balanced|lean> Change the shared MCP recall response budget. Stored memory is not modified. settings mesh synapse settings mesh &lt;on|off&gt; Turn the agent mesh tools on or off. Connected tools pick the change up the next time they start. settings price synapse settings price &lt;dollars&gt; The price per million input tokens that synapse savings and the Savings page use for the dollar figure. Defaults to $2. settings workers synapse settings workers &lt;count&gt; The most background workers one session may run at once. Every worker is a separate session on the account you already pay for, so this is a spending limit as much as a performance one. Clamped to a built-in ceiling, so a mistyped number cannot buy an unbounded fleet. A supervisor already running picks the change up on its next spawn. settings learn synapse settings learn &lt;on|off&gt; Let agents write and correct skills, adding teach and revise to every connected session. Off by default. A skill an agent writes waits in skill proposed until you approve it, so turning this on never changes how a session behaves without you having read what changed it. guidance show synapse guidance show [--json] Print the SOUL.md path, whether it exists, pointer coverage, and whether both global instruction files are pointer-only. guidance sync synapse guidance sync Create SOUL.md when needed and refresh managed pointers in both global instruction files without removing unmanaged text. guidance adopt synapse guidance adopt --confirm Move unmanaged global guidance into SOUL.md, replace both global files with managed pointers, and retain backups. Tools Synapse does not ship Codex, Claude Code, pi, and Ainz are ordinary descriptors, not special cases. A descriptor is a TOML file saying where a tool keeps its files, what to run against its own CLI to connect it, how to read that back, and which flags it takes when Synapse starts it. Yours resolve from .synapse/tools/ in a repository first, then your data directory, then the ones Synapse ships — so a project can carry the tool its team works in, and you can correct a built-in without waiting for a release. A described tool gets everything a built-in gets: connection, shared guidance, the skill library, and the mesh. Follow the custom harness guide for a descriptor example, setup verification, and contribution instructions. tool list synapse tool list [--json] Every tool this machine can connect to, with the layer each one resolves from. tool show synapse tool show &lt;name&gt; Print one descriptor and where it came from. tool create synapse tool create &lt;name&gt; Describe a tool Synapse does not ship. Opens a commented template in your editor and refuses to save a file that would not load. The name becomes the descriptor's file name and what you pass to --tool . tool edit synapse tool edit &lt;name&gt; Edit a descriptor. Editing one Synapse ships copies it into a layer you own first, so the shipped file stays as it was. tool delete synapse tool delete &lt;name&gt; Remove a descriptor you added. Deleting a copy that overrides a built-in returns you to the shipped one. Installation and paths install synapse install Install the current executable for this user. A packaged app creates a launcher into the signed bundle; a development binary is copied atomically. Unrelated destination files are never overwritten. path synapse path Print the resolved data directory, SOUL.md, and CLI destination. savings synapse savings [--days &lt;n&gt;|--all] [--json] Tokens and dollars Synapse kept out of your sessions, compared with keeping the same memory in an instruction file every session loads. Counts what each session could reach, what recall and the session hook actually sent, and what Synapse's own tool definitions and guidance cost. Defaults to the last 30 days. Usage older than 730 days is pruned as new activity is recorded. Figures are characters divided by four, counted once per session, so they understate. doctor synapse doctor [--json] Report everything a bug report needs: version, store state and size, drift found from the current folder, connected tools and what each is set up with, skill and mesh state, shell and CLI integration, resolved paths, and recent crashes. Every check reports rather than fails, so a broken store is described instead of stopping the report. Nothing is sent anywhere. connect synapse connect [tool] Wire a tool into memory and the vault: register the Synapse MCP server through that tool's own CLI, and point its global instruction file at SOUL.md. With no name it connects every tool this machine has. An existing registration that already points at Synapse is left alone. Synapse never edits a tool's configuration itself — it asks the tool to. connect --refresh synapse connect &lt;tool&gt; --refresh Apply this release's descriptor to a tool that is already connected, writing its registration again. Detection cannot tell a descriptor that moved from one that did not, so a tool set up under an older release keeps that release's answer until this is run. Nothing is removed and the tool stays connected throughout. synapse doctor reports update available when it is worth running. connect --reset synapse connect &lt;tool&gt; --reset Disconnect the tool and connect it again. Costs more than --refresh : disconnecting takes back the skills Synapse installed for that tool, and this restores the connection rather than the library. For when what is on disk has to go before the new answer can land. disconnect synapse disconnect [tool] Undo one tool's connection, or every tool's when no name is given: the MCP registration or installed package, the managed block in its instruction file, the Claude Code hooks and status line, and any skill Synapse installed for it. A skill you wrote, or a status line somebody else configured, is left alone. uninstall synapse uninstall [--data] [--confirm] Remove everything Synapse installed: every tool connection, the shell hook, and the command line tool. Without --confirm it prints what it would remove and stops. Your memory is left alone unless you also pass --data , which cannot be undone. version synapse version Print the application version. --version and -V are aliases. help synapse help [command] Print the command summary, or one command’s entries. --help and -h do the same thing wherever they appear before a -- ."},{"title":"Troubleshooting","description":"Diagnose installation, tool connection, scope, vault, memory, database, and restore failures without risking user-owned data.","path":"docs/troubleshoot/","text":"Start with status shell Copy synapse version synapse path synapse data check synapse status . synapse settings show These commands establish which executable is running, which database it opens, whether the database is healthy, which scopes apply to the current folder, and which response budget connected tools use. One command for a bug report synapse doctor gathers everything anyone would ask you for: version, store state and size, which tools are connected and what each is set up with, skill and mesh state, shell and CLI integration, resolved paths, and any crash Synapse has recorded. shell Copy synapse doctor synapse doctor --json Every check reports rather than fails, so a damaged store is described in the report instead of ending it. Synapse sends nothing anywhere — the report is printed for you to read or paste. The desktop application writes panics to <code>crash.log</code> in the data folder, because a window that disappears leaves nothing to read. The log holds only the crash itself and is bounded, so a crash loop cannot fill a disk. <code>doctor</code> prints the most recent entries. undefined App and CLI macOS will not open the app Confirm you downloaded the current archive from the release page, extracted it fully, and moved the app to Applications. The app is Developer ID signed and notarized, so a Gatekeeper refusal usually means the archive was altered or only partly extracted. Avoid modifying files inside the application bundle because that invalidates its signature. synapse: command not found Install the CLI from Settings, then add ~/.local/bin to PATH . Run the executable by full path once to verify: shell Copy ~/.local/bin/synapse version The CLI installer reports a conflict A different file already exists at the destination and does not have a matching Synapse receipt. Synapse will not replace it. Move or rename that file yourself, or set SYNAPSE_BIN to a different full destination and install again. The CLI stopped working after moving the app Packaged installations point into the signed bundle. Open Synapse at its final location and choose Install CLI again to refresh the managed launcher. Tool connection A tool is detected but not connected The registered entry may point to a deleted build or old app location, or may have arguments other than [\"mcp\"] . Choose Repair in the app. Synapse backs up the integration store before replacing the named entry. The tools do not appear after connection Restart the developer tool so it relaunches user-level MCP servers. Confirm the stored command exists and runs synapse mcp without printing shell startup text to stdout. The tools follow different global guidance Run synapse guidance show . If a pointer is missing, use synapse guidance sync or Settings → Shared guidance → Sync pointers , then restart the tools. Consolidation is optional and requires separate confirmation. Setup failed Read the app error for the external tool command that failed. Setup restores both the integration store and instruction file on failure, leaving .synapsebackup recovery copies beside changed existing files. Scopes and secrets vault scope is not ready Run synapse status . . Approve pending or changed files with synapse allow only after inspecting them. Fix invalid YAML and unknown vault.name references before trying either shell mode again. The shell hook does not activate Open Settings and check Shell environments . Enable or repair the detected hook, then open a new terminal. Run synapse status . ; it should report the current shell hook and ambient state as ready. Approve the closest scope with synapse allow . Global mappings alone never activate ambient mode. Settings reports an incomplete or duplicate hook block Synapse refuses to guess which startup-file content it owns. Open the path shown in Settings, reduce the # synapse:shell:begin and # synapse:shell:end markers to one complete pair or remove that marked block, then return to Settings and enable the hook again. An ambient value remains after leaving Press Enter once so the prompt hook reevaluates the directory. If the name existed before activation, Synapse deliberately restores that original value. Run synapse status . to distinguish a restored value from an active scope. A narrower mapping does not apply Check every ancestor scope. A broader deny permanently blocks that environment name for narrower scopes. Also confirm the nested file itself is approved and the command’s working directory is below it. Keychain access fails This applies on the Keychain store only; run synapse vault backend to see which one this machine uses. Unlock the login Keychain and retry from an interactive user session. If a label exists but its item was removed externally, set the secret again. If the credential itself may be invalid, rotate it at its issuer before storing the replacement. A denied Keychain prompt during vault migrate stops the migration with nothing moved and nothing switched, so retrying after granting access is safe. A secret has no value in the encrypted vault The label is in brain.db and the sealed value is not in vault.db . That happens when the two came from different machines, or when a value was set while the machine was on the other store. Run synapse vault backend to confirm which store is in use, synapse vault migrate to bring values across from the other one, or synapse secret set to write the value again. Nothing opens with the vault key vault.key is the only thing that opens vault.db , and a replaced key cannot be recovered from the sealed values. If the key was lost, set each value again with synapse secret set ; the labels and mappings in brain.db tell you which ones are expected. Back the two files up together, and treat that backup like the credentials it holds. The child sees a value but MCP does not This is expected. MCP reports only names, trust state, and which store this machine uses. Values are read out of the vault only for synapse run -- <command> , while an installed shell hook activates an approved directory, or when you ask for one with synapse secret copy . Memory and database Recall returns too little content Run synapse settings show . Switch to Balanced or Full if Lean is too small. Search with concrete words present in the stored body and inspect the exact entry through synapse memory show <id> . A memory that is definitely stored does not come back Make the search show its working with synapse memory list \"your query\" --explain . It prints the words it searched for and the words it dropped for matching nearly every memory, which separates a query that lost its only real term from a store that holds nothing. If what you are looking for is an exact string — a flag, an identifier, a path — use synapse memory grep instead, which matches characters and never drops a word. Also check the entry has not been superseded: synapse memory list marks a replaced memory, synapse memory show <id> names what replaced it, and synapse memory restore <id> puts it back in recall. A stored memory is wrong If the wording was bad, edit the original. If it was true and stopped being true, add the new version and run synapse memory supersede <old> <new> — recall returns the new one and the old text stays readable. If it should never have been stored, delete it. What to avoid is adding a contradictory entry and leaving both live, because recall returns both. Export a snapshot before a large cleanup. An import shows flagged entries Open the provider folder with Review source and inspect the named files. The app never imports flagged content. If a CLI import is genuinely safe, rerun it with both --include-flagged and --confirm ; otherwise move only the durable non-sensitive fact into Synapse manually. One project's memory appears missing Inspect the record's project in the Memory editor or with synapse memory show <id> , and compare it with what the current folder resolves to. A repository with an origin remote is keyed by that remote, such as github.com/owner/repo ; anything else is keyed by its root folder's path. Project recall intentionally combines global memory with one matching project and excludes every other project. Two cases account for most misses. A folder with no remote that was moved or renamed has a new path, so memory stored under the old one no longer matches: add a remote, or edit the record's project to the new path. And memory filed under a folder that no longer exists cannot be recalled from anywhere until its project is edited to a current folder or key. A stored memory says [redacted] Synapse removed a credential from it before storing: a value assigned to a secret-sounding name, a password in a URL, a bearer token, a provider token, or a private key. That is deliberate, and the value was never written to the database. Put the value in the vault and let the memory say where it lives. Anything between <private> and </private> is removed the same way. Search returns a memory that only shares part of a word Search matches word forms and three-character fragments as well as whole words, so schemas can find schema , and a long term can match inside a longer identifier. A memory that matches both ways ranks above one that matches only one way. Run synapse memory list <query> --explain to see both expressions and each hit's score, and synapse memory grep when you want exact characters only. Integrity check fails Stop using the affected database. Do not overwrite it. Preserve the whole data directory, locate the newest known-good export or automatic backup, and follow the restore procedure. A failed check is a data-recovery event, not a migration prompt. Restore Restore says Synapse is using the database Quit the desktop app and close every developer-tool session connected to the MCP server. Confirm no synapse mcp process remains, then retry. The refusal proves the exclusive lifecycle guard is working. The backup version is unsupported The source schema must match the current release. Keep the source unchanged. Open it first with the Synapse version that created it, let that version migrate normally if appropriate, export a fresh snapshot, then restore that compatible export with the current app. Secrets are missing after moving to another machine Database exports carry brain.db and never a value. Recreate each value with synapse secret set on the destination, or — on the encrypted store — copy vault.db and vault.key across together, since neither is any use without the other. Existing vault labels and references in the restored database tell you which entries are expected."},{"title":"Tutorials","description":"Eleven guided walkthroughs arranged by how far into Synapse you are, from a first connection to running agent teams and removing everything cleanly.","path":"tutorials/","text":"These are arranged by how far into Synapse you are, not by feature. Each level assumes the one before it, and each tutorial ends by pointing at the next. If you are new, start at the top and stop whenever Synapse is doing what you need — most people never leave the second level, and that is a complete way to use it. Level 1 · Newcomer You have downloaded Synapse and want one tool remembering things. Roughly an hour for both. Install and connect your first tools Start from the release archive, install the CLI, connect Codex, Claude Code, or pi, and verify all three MCP tools from the app, the terminal, and the tool itself. Carry one decision between tools Store a confirmed convention in one tool, end the session, recover it in another, and inspect the exact record that made it across. Level 2 · Daily driver Synapse is connected and you use it every day. This level is about controlling what it remembers, what it can hand to a command, and what your tools know how to do. Curate and optimize memory Add, find, correct, delete, and budget durable memory using both human-readable and JSON output. The one to read if recall is returning too much or the wrong thing. Use a scoped secret in either shell mode Create a vaulted value, compare one-command and ambient loading without printing the value, and watch trust invalidate when the file changes. Keep one skill library across every tool Write an Agent Skill once, install it into every connected tool, and see how Synapse tells a library that moved on from a copy somebody edited by hand. Let your agents write skills Let a session write down a procedure it worked out, prove for yourself that it reaches no tool until you approve it, then correct it and take the correction back. Level 3 · Team operator You want several agents working at once on one job. This is the deepest level, and the one where agents touch real files on your machine — use a checkout you can restore. Start a tool with everything in place Open a tool with memory, scoped credentials, and the project root already wired, without writing anything into that tool's own configuration. Run a team of agents and drive it yourself Turn on the mesh, open a team, read what every agent is doing from one terminal, answer a worker that gets stuck, and shut it all down cleanly. Hand a job to one agent and watch it grow a team Describe an outcome instead of picking a roster, answer the question only you can answer, and reach a worker directly without going through the agent that started it. Level 4 · Maintainer You are responsible for the store staying sound, or you are handing the machine on. Read these before you need them. Export and restore safely Create a validated snapshot, make a reversible change, acquire the exclusive lifecycle lock, restore, and verify recovery. Check, migrate, and remove Synapse Read a full health report, understand what a schema migration does, disconnect one tool, and remove everything while keeping your memory and your own files. What every tutorial includes Each one names its prerequisites, builds one complete outcome, includes verification you can observe rather than assume, calls out the security boundary it crosses, and ends with cleanup or the next safe step. Commands assume the installed synapse CLI is on PATH . If you need exact command syntax outside a guided workflow, use the CLI reference . If a step fails, stop there and use Troubleshooting rather than skipping a trust or integrity check — those checks are the reason the destructive steps later are safe."},{"title":"Install and connect your first tools","description":"Go from the signed release archive to a working local MCP connection, and verify the same memory from the desktop app, the terminal, and the coding tool itself.","path":"tutorials/connect/","text":"Outcome and prerequisites You will install Synapse, install its command-line tool, connect at least one coding tool, store one harmless memory, and see that same record through three separate surfaces. By the end you will also know exactly which files were touched and how to take every one of them back. For this desktop walkthrough: an Apple-silicon Mac running macOS 13 or later. Linux users can follow the terminal setup guide , then try a memory handoff . Codex, Claude Code, or both installed and on PATH . The latest synapse.zip release archive. About twenty minutes. Move the app somewhere permanent, then open it Extract synapse.zip , move synapse.app to Applications, and open it. The app is Developer ID signed and notarized, so it opens without a security override and without a right-click workaround. Choose the location before the next step The CLI installed from a packaged app launches the executable inside that signed bundle. Install the CLI only after the app is where you intend to keep it, or the command will point at a path that no longer exists. If you move the app later, the dashboard offers Repair — it recognizes a stale executable path rather than reporting a healthy connection. Install the CLI Open Synapse Settings and choose Install CLI , or run the installer from inside the bundle. Either way it reports where it landed: text Copy Installed /Users/example/.local/bin/synapse Add /Users/example/.local/bin to PATH to use synapse from your shell. Open a new terminal and confirm the shell can find it: shell Copy synapse version synapse path text Copy synapse 0.1.0-beta.24 text Copy data /Users/example/Library/Application Support/synapse soul /Users/example/Library/Application Support/synapse/SOUL.md cli /Users/example/.local/bin/synapse If the shell cannot find synapse , add ~/.local/bin to PATH and start another shell. The data path above must match what the app reports on its Settings screen — if the two differ, something has set SYNAPSE_DATA in one context and not the other, and they are reading different stores. Connect a detected tool Return to the dashboard. Each supported tool appears as a row with its state. Choose Connect beside Codex, Claude Code, or pi, and the row moves from detected to connected. Connect the others too if you have them — the whole point is one memory behind all of them. Detection is not a guess. Synapse parses the actual entry named synapse in the tool's own configuration and reports connected only when the stored command resolves to the expected executable and its arguments are exactly [\"mcp\"] . A tool whose binary was deleted or moved reads as stale, not healthy. Know what connecting actually wrote This is worth understanding now rather than the first time something looks wrong. Connecting is not a black box — it is four specific changes, and every one is reversible. Change Where How it is made The MCP server ~/.codex/config.toml or ~/.claude.json Through the tool's own mcp add command, not by editing its file directly. A guidance pointer ~/.codex/AGENTS.md or ~/.claude/CLAUDE.md Appended inside a managed block. Your own text is untouched. A session hook ~/.claude/settings.json Claude Code only. Runs synapse session at session start. A status line ~/.claude/settings.json Claude Code only, and only if you do not already have one. Yours is reported, never replaced. Every changed file gets a .synapsebackup sibling before it is replaced, and the whole operation rolls back if any step fails. Check the shared guidance file was created: shell Copy synapse guidance show text Copy SOUL.md /Users/example/Library/Application Support/synapse/SOUL.md exists true pointers 2/2 consolidated false A full count — pointers 3/3 with all three tools connected — means each one points at a single editable SOUL.md . That file is yours to edit; it is where shared guidance for every connected tool lives. Restart the tool and read its first line Close and reopen the connected tool so it loads the new MCP server. A running session keeps the tool list it started with, so this step is not optional. Claude Code will print a line beside its welcome box before the model has written anything: text Copy Synapse connected · no memories yet That line comes from the session hook, which is the only way to state the connection before the first reply. It also hands the session this project's memory directly, so a Claude Code session starts already holding what the project has decided rather than being asked to go and look. On an empty store there is nothing to hand over yet, which is what you are seeing. Codex has no session hook, so it reports the connection in its first reply instead, following the guidance in SOUL.md . Either way, ask the tool what Synapse tools it has. The answer should include remember , recall , readmemory , and vaultstatus — four core tools. The sixteen mesh tools appear only when you switch the mesh on. Store one memory Tell the connected tool: Remember this confirmed tutorial convention: documentation examples use the source label synapsetutorial. It should call remember with the current project root and report a numeric ID. Use an ordinary convention for this — memory is plain text readable by every connected tool, so never put a token, password, or private key in it. Synapse removes the ones it recognizes before storing and replaces them with [redacted] , but that is a backstop, not a reason to try. Verify the same record three ways One store, three surfaces. Check all of them, because a mismatch here is the clearest signal that something is pointed at the wrong place. shell Copy synapse memory list synapsetutorial text Copy 1 project:/Users/example/project synapsetutorial Documentation examples use the source label synapsetutorial. The columns are the ID, the scope and project it belongs to, the source label, and the body. Now the exact record: shell Copy synapse memory show 1 text Copy Memory #1 Scope: project Project: /Users/example/project Source: synapsetutorial Created: 1785776940 Documentation examples use the source label synapsetutorial. Finally, open the Memories screen in the app and search for synapsetutorial . The body, source, and scope must match what the terminal just printed. At this point the coding tool, the CLI, and the desktop app are provably reading one local database. If something is not right Symptom What it usually means The tool lists no Synapse tools It was not restarted after connecting. Close it fully and reopen. The dashboard row says stale The app moved after the connection was made. Choose Repair . synapse : command not found ~/.local/bin is not on PATH , or the shell has not been restarted. CLI and app show different memory counts They are on different data directories. Compare synapse path with the app's Settings screen. The tool reports a connection that is not there Run synapse doctor . It reports what is actually configured rather than what should be. shell Copy synapse doctor That one command is also what to attach to a bug report. It carries no memory contents and no secret names, so it is safe to paste into a public issue. Undoing all of it shell Copy synapse disconnect claude # or codex, or nothing for every tool Disconnecting removes only what Synapse wrote: the MCP entry, the managed block in the instruction file, the session hook, and the status line — and only if that status line is the one Synapse installed. Your own words in CLAUDE.md , your own status line, and every memory survive it. Removing memory is a separate, explicit decision covered in Check, migrate, and remove Synapse . Next step Continue with Carry one decision between tools to prove a real handoff across a session boundary and a tool boundary."},{"title":"Carry one decision between tools","description":"Make a real project convention durable in one session, recover it in another tool after closing the first, correct it at the source, and see the scope rule keep other projects out.","path":"tutorials/continuity/","text":"Outcome and prerequisites You will prove that durable context survives two boundaries a conversation cannot: the end of a session, and the move to a different tool. Then you will correct it at the source and confirm that another project cannot see it. Install and connect your first tools completed. Two connected tools make the handoff visible. Two separate sessions of one tool prove persistence just as well. A real project folder, and a second one for the scope check. Choose one decision worth keeping Use something concrete, harmless, and genuinely load-bearing for future work. A package-manager choice, a supported deployment target, a naming rule, or a correction to an assumption that turned out wrong are all good. The test of a good memory is whether a session that did not know it would do something different. Worth remembering Not worth remembering \"Use Bun for JavaScript tasks in this repository.\" \"The user asked me to check the tests.\" \"Releases are triggered by a version bump on main.\" \"The build is currently failing.\" \"Do not hand-edit the generated site directory.\" A full transcript of how you reached the decision. The right-hand column is transient. It is true today and misleading next week, and a memory store full of it makes recall worse rather than better. Never stage a fake secret Memory is plain text available to every connected tool. Use an ordinary project convention, never a token, credential, or private key — not even a made-up one, because a habit formed here is a habit that shows up in a real store. Confirm it, then have the tool remember it In the first connected tool, establish the decision explicitly, then ask for it to be stored: We have confirmed that this repository uses Bun for JavaScript tasks. Remember that durable convention with source synapsetutorial. The tool should call remember once, with project scope and this repository's root, and return an ID. Two things can go wrong here and both are worth noticing: It stores a transcript instead of the decision. A memory should be one durable idea in one or two sentences. You will fix this in the correction step. It stores global scope instead of project. Global memory is returned in every project, which is right for a preference about how you like to work and wrong for a fact about one repository. Close the session completely Exit the first tool. Not a new conversation inside it — exit it. This is what rules out conversational context and leaves the local database as the only thing carrying the decision forward. Recall it from the other tool Open the second connected tool in the same repository. If it is Claude Code, look at what happens before you type anything: the session hook has already recalled this project's memory and handed it over, so the convention is in context from the first turn. That is the difference between memory that works and memory that depends on the model remembering to ask. Either way, ask a question the convention should change the answer to: What command should I use to install dependencies here? It should answer with Bun rather than npm, and be able to say where that came from. If it guesses wrong, ask it explicitly to recall the JavaScript tooling convention for this project — that tells you whether the memory is missing or merely was not consulted. Inspect the source of truth and correct it What the tool reported is a rendering. Go and look at the record itself: shell Copy synapse memory list "Bun JavaScript" text Copy 4 project:github.com/example/project synapsetutorial We confirmed together that this repository uses Bun for JavaScript tasks rather than npm, after discussing… shell Copy synapse memory show 4 If the body is a paragraph of conversation rather than a durable statement, replace it. Editing keeps the same ID, so nothing else has to change and no contradicting second record is created: shell Copy printf '%s\\n' 'Use Bun for JavaScript tasks in this repository.' \\ | synapse memory edit 4 synapsetutorial text Copy Updated memory #4 Start one more new session and ask again. The answer should now reflect the corrected text — which proves that editing changed the durable source rather than layering a correction on top of a wrong record. Correcting beats appending Two memories that disagree are worse than one that is slightly wrong, because recall returns both and the model has to guess which is current. When the wording was bad, edit the record. When the old version was true and stopped being true, add the new one with synapse memory supersede so the old text stays readable but out of recall. Add a plain second memory only when it is genuinely a new fact. Prove other projects cannot see it This is the property that makes a shared store usable. Open a session in an unrelated project and ask the same question: What command should I use to install dependencies here? It must not answer with the first project's convention. Confirm from the terminal that the record is scoped where you think it is: shell Copy synapse memory show 4 text Copy Memory #4 Scope: project Project: github.com/example/project Source: synapsetutorial Recall from any project returns everything global plus everything stored for that project. Another project's memory never enters the response. The project root is resolved by walking up for a .git directory or a .synapse.yaml , so a session opened in a subdirectory still resolves to the same root — which is why the same memory is found whether you start at the repository top or three folders down. The project is then named by the repository's origin remote, which is why it reads github.com/example/project rather than a folder. Clone the repository somewhere else, or open a worktree of it, and a session there recalls the same memory. A folder with no remote is named by its path instead, and memory filed under it stays with that path. Store one thing globally, and see the difference Some things belong everywhere. A preference about how you like to work is not a fact about one repository: shell Copy printf '%s\\n' 'Prefer small, focused commits with imperative subject lines.' \\ | synapse memory add synapsetutorial --global text Copy Stored memory #5 Now recall from both projects. The global preference appears in each; the Bun convention appears in only one. That split — global plus this project, never another project — is the entire scope model, and it is enforced in the query rather than trusted to the caller. Keep or clean up If the convention is real, keep it — you have just made your tools better at this repository. If it was tutorial-only, remove the exact IDs: shell Copy synapse memory list synapsetutorial synapse memory delete 4 --confirm synapse memory delete 5 --confirm Deleting requires --confirm and names the record it is about to remove. Avoid synapse memory wipe unless you genuinely intend to remove every memory in the store; it exists for starting over, not for tidying. Next step You have finished the newcomer level. Continue with Curate and optimize memory , which is about keeping a store useful once it has more than a handful of entries in it."},{"title":"Curate and optimize memory","description":"Build a small memory set, learn how search actually behaves, correct entries at the source, tune the recall budget, import existing notes reversibly, and remove only what you added.","path":"tutorials/curate/","text":"Outcome and prerequisites You will build a small set of entries, understand why search returns what it returns, correct one at the source, compare what each recall budget actually sends to a model, import a Markdown file reversibly, and then undo it. This is the tutorial to read when recall is returning too much, too little, or the wrong thing. The synapse CLI installed and at least one tool connected. A project folder. Steps here create and delete entries under one source label. Write down the IDs Every add prints its new numeric ID. Keep those numbers for the cleanup step. Do not reach for synapse memory wipe in a store that has real context in it — it removes everything, and it is meant for starting over rather than tidying. Add focused entries Content is read from stdin, never from an argument, so a memory can contain anything without shell quoting getting in the way: shell Copy printf '%s\\n' 'Tutorial apps use port 4100.' | synapse memory add synapsetutorial printf '%s\\n' 'Tutorial commands use Bun.' | synapse memory add synapsetutorial printf '%s\\n' 'Tutorial backups go in the backups folder.' | synapse memory add synapsetutorial text Copy Stored memory #1 Stored memory #2 Stored memory #3 synapsetutorial is the source — a label saying where the fact came from. Give every memory one. It is what lets you find a set later, audit what a particular session stored, and delete a batch without hunting. One durable idea per entry. Three entries about ports, runtimes, and backups can each be recalled, corrected, and deleted on their own; one entry containing all three is returned whole every time any part of it is relevant, and cannot be corrected without rewriting the rest. Search and inspect shell Copy synapse memory list tutorial text Copy 2 project:/Users/example/project synapsetutorial Tutorial commands use Bun. 1 project:/Users/example/project synapsetutorial Tutorial apps use port 4100. The columns are the ID, the scope with its project, the source, and the body. For anything scripted, ask for JSON instead: shell Copy synapse memory list "Bun" --json text Copy [ { "id": 2, "body": "Tutorial commands use Bun.", "source": "synapsetutorial", "scope": "project", "project": "/Users/example/project", "created": 1785776940 } ] And for one exact record, with nothing trimmed: shell Copy synapse memory show 2 text Copy Memory #2 Scope: project Project: /Users/example/project Source: synapsetutorial Created: 1785776940 Tutorial commands use Bun. Understand how search behaves Recall is full-text search with a few behaviors worth knowing, because each explains results that otherwise look wrong. Words match in other forms, and so do fragments. The query is searched twice: once by word, stemmed, so deploying finds a memory that says deploys ; and once by three-character pieces, so hashpw finds scripts/hashpw.sh . A memory that matches both ways ranks above one that matches only one. Its source is searched too, at a lower weight, so the label you file a memory under helps find it. shell Copy synapse memory list deploying synapse memory list synapsetutorial Common words are dropped. A query is stripped of words that carry no meaning on their own. Try it: shell Copy synapse memory list "what are the" Nothing in that query survives, so instead of matching every memory containing the , Synapse answers the way it answers an empty query — with the most recent entries. That is deliberate. Matching on a common word is both wrong, because a memory that happens to contain are is not the answer to anything, and slow, because a common word makes the index rank most of the store. An empty query is a real question. It means \"what should I know here\", and it returns the most recent memory in scope. This is exactly what a session opening asks, and it is what the Claude Code session hook uses. shell Copy synapse memory list Words that carry a preference are kept. not , never , and use look like function words but change the meaning of a convention completely, so they stay in the query. \"Never use npm\" and \"use npm\" must not search identically. You can ask what it did. When a memory you know is stored does not come back, the fastest answer is to make the search show its working: shell Copy synapse memory list "what are the credentials" --explain text Copy Query: what are the credentials Mode: search Expression: "credentials" Fragments: "credentials" Searched: credentials Dropped: what, are, the (matches nearly every memory) Matches: 0 That distinguishes the two things that look identical from the outside: a query that lost its only real word, and a store that genuinely holds nothing. And you can skip the ranking entirely. When you want an exact string — a flag, an identifier, a path — the ranked search is the wrong tool, because the thing you are looking for may be a word it drops: shell Copy synapse memory grep -- --no-verify And you can measure it. Once a project has memory worth keeping, write down a few questions you expect it to answer, with text the right answer contains, and score recall against them. Run it again after a cleanup and the numbers say whether the store got easier to search or harder: toml Copy [[case]] query = "which javascript runtime" project = "/Users/example/project" expect = ["bun"] shell Copy synapse memory eval cases.toml Measuring recall explains each number, including how often the answer survived the response budget. Scope is applied in the query, not after it Every search returns everything global plus everything for the current project, and nothing from any other project. That is enforced where the rows are selected rather than filtered afterwards, so there is no path where another project's memory is read and then discarded. Correct at the source A convention becomes more precise. Replace the record rather than adding a second one that disagrees with it: shell Copy printf '%s\\n' 'Tutorial commands use Bun unless a task requires another runtime.' \\ | synapse memory edit 2 synapsetutorial synapse memory show 2 text Copy Updated memory #2 The ID is stable, so anything referring to this memory still refers to the right thing, and future recall returns only the corrected text. Two entries that contradict each other are the single most common way a memory store gets worse as it grows: recall returns both and the model has to guess which is current. Editing is right when the old wording had nothing worth keeping. When the old version was true and then stopped being true , supersede it instead — the new memory becomes the one recall returns, and the old one stays readable as the record of what used to be the case: shell Copy printf '%s\\n' 'Tutorial commands use Bun. Node is used only for the release script.' \\ | synapse memory add synapsetutorial synapse memory supersede 2 6 text Copy Memory #2 superseded by #6; recall now returns #6 instead Undo with: synapse memory restore 2 Check both. Memory 2 is still there, marked, and out of recall; memory 6 is what a tool now sees. If you decide you were wrong, synapse memory restore 2 puts it straight back. shell Copy synapse memory show 2 synapse memory list Tune what recall actually sends The budget controls how much a recall response costs a model in context. Look at where you are: shell Copy synapse settings show text Copy optimization balanced result limit 8 character budget 6000 mesh off shell modes command-scoped, ambient directory zsh hook eval "$(synapse hook zsh)" Budget Results Characters When to use it Full 25 unbounded A small store, or when you want everything and context is cheap. Balanced 8 6,000 The default, and the right answer for most stores. Lean 4 2,800 A large store, a long session, or a model whose context you are protecting. shell Copy synapse settings optimize lean text Copy Recall optimization set to Lean Ask a connected tool to recall synapsetutorial and note how many entries come back. Switch to full and ask again. The stored bodies never change — synapse memory show returns the same text at every setting. The budget only shapes the response. A tool can request a smaller budget for one call, and that request can only ever shrink your configured ceiling. A session cannot talk its way into a larger response than you have allowed. shell Copy synapse settings optimize balanced Import notes you already have If you have been keeping conventions in a Markdown file, bring them in. Every import previews first: shell Copy synapse memory import markdown notes.md text Copy Markdown: 1 found · 1 ready · 0 existing · 0 flagged ready notes.md # Conventions - Use Bun, never npm. - Deploys target Apple silicon only. … Preview only. Add --confirm to import safe entries. Now try one with a credential in it and watch what happens: text Copy Markdown: 1 found · 0 ready · 0 existing · 1 flagged review flagged.md Content hidden until the source file is reviewed. warning: mentions a credential-shaped environment variable Preview only. Add --confirm to import safe entries. Flagged content is not merely skipped — it is not even displayed, because printing it to your terminal to warn you about it would defeat the point. --confirm imports the ready entries and leaves flagged ones untouched. The flagging is conservative, not exhaustive It looks for private-key markers, credential-shaped variable names, assignments to password- and token-like keys, and bearer tokens. A secret that matches none of those patterns will not be caught. Read your own preview — the flag is a safety net, not a substitute for looking. shell Copy synapse memory import markdown notes.md --confirm text Copy Import batch #1: 1 stored, 0 linked, 0 already imported, 0 flagged and left untouched Undo with: synapse memory undo 1 --confirm The entries that are imported are redacted on the way in, like every memory Synapse stores: a credential that slipped past the flag becomes [redacted] rather than being written to the database. Imports are idempotent. Running the same file again reports the entries as already imported rather than creating duplicates, so a repeated import is harmless. Claude and Codex memory stores import the same way with synapse memory import claude and synapse memory import codex . Undo the whole batch shell Copy synapse memory imports text Copy 1 markdown 1 stored 0 linked active shell Copy synapse memory undo 1 --confirm synapse memory imports text Copy Undid import batch #1; removed 1 imported memories text Copy 1 markdown 1 stored 0 linked undone The batch is recorded rather than forgotten, so an import you regret is one command to reverse and the record of it having happened survives. Anything you wrote by hand is untouched — undo removes only what that batch stored. Delete only what you added shell Copy synapse memory list synapsetutorial synapse memory delete 1 --confirm synapse memory delete 2 --confirm synapse memory delete 3 --confirm text Copy Deleted memory #1 Both destructive commands refuse without --confirm , and say which record they mean: text Copy Error: add --confirm to delete memory #3 Error: add --confirm to delete every memory A final synapse memory list synapsetutorial should return nothing. Use the Memories screen for a visual review before deleting anything you did not create in this tutorial. Next step Continue with Use a scoped secret in either shell mode , which is the other half of what Synapse hands a tool — and the half where the boundaries matter most."},{"title":"Use a scoped secret in either shell mode","description":"Create a vaulted value, approve its project mapping, compare one-command and ambient loading without ever printing it, and watch trust invalidate the moment the file changes.","path":"tutorials/secrets/","text":"Outcome and prerequisites You will create a disposable value in the vault, map it to one project, and exercise both ways Synapse can hand it to something — without the value ever appearing on screen. Then you will break the approval on purpose and watch every path refuse. The synapse CLI installed. If synapse vault backend says keychain , unlock your login Keychain first. An empty temporary folder. Do not run this in a real project. About twenty-five minutes. Where the value actually lives Worth being precise about before you start, because the whole design follows from it. Thing Stored where The secret value The value store, and only there. That is vault.db — sealed, one envelope per secret, under the key in vault.key — or macOS Keychain if the machine is set to it. synapse vault backend says which. Its name, its variable name, its account reference brain.db , Synapse's SQLite database. Never the value. Which variable a folder should get .synapse.yaml in the project, which holds a reference and never a value. Whether that file is trusted A SHA-256 digest of its exact bytes, recorded when you approve it. No secret value is ever written to brain.db , to YAML, to an MCP response, or to a log, and neither store keeps one in plaintext. A value is read out of the vault at the moment a child process is launched, and goes nowhere else unless you ask for it with synapse secret copy , which puts it on the clipboard and still never prints it. Create a vault and a value shell Copy mkdir -p "$HOME/tmp/synapsetutorial" cd "$HOME/tmp/synapsetutorial" synapse vault create tutorial synapse secret set tutorial demo SYNAPSE_TUTORIAL_TOKEN Enter a disposable value at the hidden prompt and confirm it. A vault is an organizational label; the three arguments are the vault, the secret's name inside it, and the environment variable it will become. text Copy Created vault tutorial Saved tutorial.demo in the encrypted vault Now list what exists. Metadata only — there is no command that prints a value, because there is no reason for one to exist: shell Copy synapse secret list tutorial text Copy tutorial.demo SYNAPSE_TUTORIAL_TOKEN scoped Secrets are never command arguments synapse secret set reads from a TTY prompt or stdin, never from an argument. An argument would land in your shell history, in the process table, and in any terminal recording — three places a value should never be. Map it to this folder and approve the file shell Copy synapse scope init . Replace the generated .synapse.yaml with: yaml Copy version: 1 scope: project env: SYNAPSE_TUTORIAL_TOKEN: tutorial.demo deny: [] tutorial.demo is a reference, not a value. This file is safe to commit — that is the point of it holding a reference. Look at the file, then approve its exact bytes: shell Copy synapse scope status . synapse allow synapse status . text Copy Allowed /Users/example/tmp/synapsetutorial/.synapse.yaml text Copy Folder: . Available: SYNAPSE_TUTORIAL_TOKEN Ambient: ready /Users/example/tmp/synapsetutorial/.synapse.yaml [project · approved] Ambient: ready means a shell hook would activate here. It does not mean one is installed. Hand it to exactly one command This is the narrower of the two boundaries and the one to prefer. Verify presence without printing content: shell Copy synapse run -- sh -c 'test -n "$SYNAPSE_TUTORIAL_TOKEN" && echo "tutorial token available"' The child prints only the fixed sentence. Confirm the parent shell was never touched: shell Copy test -z "$SYNAPSE_TUTORIAL_TOKEN" && echo "parent unchanged" Synapse read the stored value, set it on that one child, and the child exited with it. Nothing was exported into your session. The child is fully trusted with the value Any process launched through synapse run can read and disclose what it is given. This tutorial uses a disposable value and a child that only tests whether it is non-empty. The boundary Synapse enforces is which processes receive a value, not what they do with it afterwards. Activate the whole directory For a permanent setup, use Settings → Shell environments → Enable shell hook and open a new terminal. To try it immediately in this shell: shell Copy # zsh eval "$(synapse hook zsh)" # bash: eval "$(synapse hook bash)" # fish: synapse hook fish | source The approved scope activates at once. Now walk in and out of it: shell Copy test -n "$SYNAPSE_TUTORIAL_TOKEN" && echo "ambient token available" cd .. test -z "$SYNAPSE_TUTORIAL_TOKEN" && echo "ambient token unloaded" cd synapsetutorial test -n "$SYNAPSE_TUTORIAL_TOKEN" && echo "ambient token restored" All three print. Leaving the directory unloads exactly the variables the hook loaded — and if one of them had a value before activation, leaving restores that original value rather than unsetting it. The hook tracks which keys it owns precisely so it can put things back as it found them. The entire shell is trusted now Every process launched from this shell can read the value, including ones you did not think about. Prefer synapse run for anything sensitive, and keep shell tracing off while the hook evaluates — a traced shell prints what it is setting. Break the approval and watch everything refuse Approval is bound to the exact bytes of the file. A blank line is a different file: shell Copy echo "" >> .synapse.yaml synapse status . text Copy Folder: . Available: none Ambient: blocked /Users/example/tmp/synapsetutorial/.synapse.yaml [project · pending] warning: /Users/example/tmp/synapsetutorial/.synapse.yaml: Scope has not been approved Three things happen at once. Ambient mode unloads at the next prompt. synapse run refuses before launching anything. And synapse launch refuses to start a coding tool at all: shell Copy synapse run -- sh -c 'echo should-not-run' text Copy Error: vault scope is not ready: /Users/example/tmp/synapsetutorial/.synapse.yaml: Scope has not been approved It refuses rather than running with the variable missing, because a command that runs against a half-resolved environment fails in ways that look like success. Read what changed, then re-approve: shell Copy synapse scope status . synapse allow Both presence tests work again. This is what makes a committed .synapse.yaml safe: a teammate's edit, or a malicious one, arrives untrusted and stays that way until a human looks at it. Prove that deny wins Scopes resolve from the root of your filesystem down to the current folder, so a narrower file can add variables. It can never add back one a broader scope denied. Move the variable into deny : yaml Copy version: 1 scope: project env: {} deny: - SYNAPSE_TUTORIAL_TOKEN shell Copy synapse allow synapse status . Available no longer lists the name. Create a folder-level scope beneath this one that tries to map it again, approve that too, and it still will not appear. Deny is a ceiling, not a default. See why global is not the same as ambient A secret can be marked global, meaning it is available without any scope file naming it: shell Copy synapse secret global tutorial.demo on synapse secret list tutorial text Copy tutorial.demo is now global text Copy tutorial.demo SYNAPSE_TUTORIAL_TOKEN global Now move to a folder with no .synapse.yaml at all and check: shell Copy cd ~ && synapse status . text Copy Folder: . Available: SYNAPSE_TUTORIAL_TOKEN Ambient: inactive Read those two lines together, because the difference between them is a deliberate safety property. The name is available — synapse run here would provide it. But ambient is inactive : a shell hook will not load it, because an ambient environment never activates from a global mapping alone. Turning on a global secret cannot silently populate every shell you open. shell Copy synapse secret global tutorial.demo off Check what a connected tool can actually see Go back to the tutorial folder and ask a connected coding tool to call vaultstatus . It reports the variable names available here, the trust state of each scope, and whether ambient mode is ready — and no values, because that tool has no code path that reads one. The same is true of a preview. synapse launch claude --print shows DEMO_TOKEN=<from the vault> rather than a redacted value, because a preview calls a different function that lists names and never opens the vault at all. There is no value in the process to leak. Clean up shell Copy synapse deny synapse secret forget tutorial.demo synapse vault delete tutorial cd .. && rm -rf synapsetutorial text Copy Forgot tutorial.demo Deleted vault tutorial The next prompt unloads the ambient value. Forgetting the secret removes the stored value and Synapse's record of it, from both stores rather than only the one in use — so a machine that has switched backends leaves nothing behind in the one it stopped reading. What you can rely on Secret values never reach brain.db , YAML, an MCP response, or a log, and are never at rest in plaintext. Secrets are never accepted as command arguments — only from a TTY prompt or stdin. An ambient shell never activates from a global mapping alone, nor from an unapproved, changed, or incomplete scope. Unloading restores values that existed before activation rather than unsetting them. Any scope warning refuses run and launch outright rather than proceeding with part of the environment. Next step Continue with Keep one skill library across every tool to finish the daily driver level."},{"title":"Keep one skill library across every tool","description":"Write an Agent Skill once, install it into every connected tool together, and watch Synapse tell the difference between a library that moved on and a copy somebody edited by hand.","path":"tutorials/skills/","text":"Outcome and prerequisites You will write one Agent Skill, install it into every connected tool from a single source, and then deliberately create both kinds of drift so you can see how Synapse tells them apart. By the end you will know exactly when Synapse will overwrite a file and when it will refuse. The synapse CLI installed and on PATH . Codex, Claude Code, or both installed. They do not need to be connected for this tutorial — skills are copied into each tool's own folder, not through MCP. About fifteen minutes. Nothing here touches your memory store. If you have not connected a tool yet, start with Install and connect your first tools . See what is already there shell Copy synapse skill list Synapse ships one skill, so a fresh library is not empty: text Copy synapse-mesh 1 Run a team of coding agents on the Synapse mesh. Use when a job is large enough to split a… The columns are the skill name, how many tools have it installed, and the start of its description. That description is the part an agent reads to decide whether the skill is relevant, so it matters more than the body: a skill with a vague description never gets loaded. Why one library at all Claude Code reads personal skills from ~/.claude/skills and Codex reads them from ~/.agents/skills . A skill you want in both gets copied twice, and the two copies start drifting the first time you improve one of them. Synapse keeps the original and installs from it. Write a skill shell Copy synapse skill create release-checklist The command reports where it landed and what to do next: text Copy Created ~/Library/Application Support/synapse/skills/release-checklist/SKILL.md Edit it, then run `synapse skill install release-checklist`. Open it with synapse skill edit release-checklist , which uses $EDITOR , or edit the file directly. Replace the template with something real: markdown Copy --- name: release-checklist description: Walk the release checklist for this project. Use when cutting a release, tagging a version, or when the user asks what still needs doing before shipping. --- ## Before you tag 1. Run the full test suite and paste the failure count, not a summary. 2. Confirm the version in the manifest matches the tag you are about to push. 3. Check that generated output was rebuilt from source rather than hand-edited. ## After the build Report the published artifact and where it can be downloaded. If notarization was skipped, say so explicitly rather than reporting success. Two rules the frontmatter has to follow: name must match the directory name, and a description containing a colon followed by a space has to be quoted or YAML reads it as a nested key. Synapse reports either problem by name rather than passing the raw parser error through. A skill is a directory, not a file scripts/ , references/ , and assets/ beside the SKILL.md travel with it and are copied whole. Agents load only the name and description until a task calls for the skill, so a long reference file costs nothing until it is actually needed. Install it into every tool at once Check where things stand first. Every skill is listed against every detected tool: shell Copy synapse skill status text Copy release-checklist Codex not installed synapse-mesh Codex not installed release-checklist Claude Code not installed synapse-mesh Claude Code not installed Now install everything, into everything: shell Copy synapse skill install text Copy release-checklist → Codex synapse-mesh → Codex release-checklist → Claude Code synapse-mesh → Claude Code Run synapse skill status again and all four rows read installed . Confirm with your own eyes that two real copies exist: shell Copy ls ~/.claude/skills/release-checklist/ ls ~/.agents/skills/release-checklist/ You can narrow either axis. synapse skill install release-checklist does one skill into every tool; synapse skill install --tool claude does every skill into one tool. Codex does not read <code>~/.codex/skills</code> for this That folder holds the set Codex ships with. Personal skills live in the shared ~/.agents/skills location, and that is the only place Synapse writes. Let the library move on This is the ordinary case: you improve the skill and the installed copies fall behind. Add a line to the library copy: shell Copy synapse skill edit release-checklist # add anything to the body synapse skill status text Copy release-checklist Codex update available synapse-mesh Codex installed release-checklist Claude Code update available synapse-mesh Claude Code installed update available means Synapse installed this copy and the library has changed since. Installing again brings them back into step, with no prompt and no flag: shell Copy synapse skill install release-checklist synapse skill status All rows read installed again. Nothing was at risk here, because the copy being overwritten was one Synapse wrote and had not been touched since. Now edit an installed copy by hand This is the case that matters. Edit the copy inside Claude Code's folder rather than the library: shell Copy echo "" >> ~/.claude/skills/release-checklist/SKILL.md echo "## A note I added directly in the tool" >> ~/.claude/skills/release-checklist/SKILL.md synapse skill status text Copy release-checklist Codex installed release-checklist Claude Code changed in place Ask Synapse to install over it and it refuses, naming the skill and the tool: shell Copy synapse skill install release-checklist --tool claude text Copy warning: release-checklist → Claude Code: `release-checklist` in Claude Code was changed in place — pass the replace option to overwrite it Error: 1 skill install(s) did not happen That refusal is the whole point of the feature. Synapse records the digest it wrote and the library digest it came from; without that record, \"the library moved on\" and \"somebody wrote this by hand\" look identical, and the only safe response to both would be to do nothing. With it, the first case is silent and the second stops and asks. When you have decided the local edit is expendable: shell Copy synapse skill install release-checklist --tool claude --replace The refusal is per skill, not per run A run that installs ten skills and hits one changed copy installs the other nine and reports the one it did not. The exit status is non-zero so a script notices, but nothing is left half-applied. Adopt a skill you already wrote A skill Synapse did not install is never written over and never deleted — but it is also never kept in step, because Synapse has no claim on it. Adopting one changes that. It copies the skill into the library and records the tool it came from as already having it, so the original stops reading as somebody else's: shell Copy synapse skill status # look for "not in the library" synapse skill adopt my-existing-skill --tool claude synapse skill list # it is yours now synapse skill install my-existing-skill # and now it reaches the other tool too Adopting copies. It does not move or delete the original, so a mistake here costs you nothing. Clean up shell Copy synapse skill remove release-checklist # take it out of every tool synapse skill delete release-checklist --confirm # and out of the library The order matters. Deleting from the library does not remove copies already installed — Synapse would then be deleting files it can no longer prove it wrote. Remove first, delete second, and nothing is left behind. Leave synapse-mesh where it is if you plan to continue; the mesh tutorial uses it. What you can rely on A skill Synapse did not install is never overwritten or deleted, in either direction. Only personal skills are managed. A repository's own .claude/skills or .agents/skills belongs to that repository and is left alone. A SKILL.md that does not parse is reported and skipped rather than copied anywhere in a broken state. synapse disconnect and synapse uninstall take back only skills with an install record. Everything you wrote survives both. Next step Continue to Start a tool with everything in place , which is the first tutorial in the team operator track, or go back to the tutorial index to pick a different level."},{"title":"Let your agents write skills","description":"Turn on self-improvement, watch a session write down a procedure it worked out, approve it deliberately, then correct it and put the correction back.","path":"tutorials/learn/","text":"Outcome and prerequisites You will let a session write a skill, prove for yourself that it reaches no tool until you say so, approve it, correct it, and undo the correction. By the end you will know exactly what an agent can and cannot do to your skill library. The synapse CLI installed and on PATH . Claude Code, Codex, or pi connected. Install and connect your first tools if not. About twenty minutes. Nothing here touches your memory store. Read Keep one skill library across every tool first if the library itself is new to you — this tutorial assumes you know what installing a skill does. Turn it on shell Copy synapse settings learn on synapse settings show | grep learn Two tools, teach and revise , appear in every connected session the next time it starts, along with the guidance explaining them. Both arrive and leave together — a tool a session cannot explain is one it will use wrongly. Why it is off by default Two more tool definitions cost context in every session that loads them, whether or not they are used. Somebody who does not want agents editing a library should never pay for the option. Let a session teach you something Start a connected tool and give it a job with a procedure buried in it — something with steps you would otherwise re-derive. A release, a tricky deploy, a debugging path that took three wrong turns. When it finishes, ask it plainly: text Copy That took a while to work out. Write it down as a skill so the next session starts from it. It calls teach with a name, a one-line description, and the steps as it would give them to somebody doing this for the first time. Synapse writes the frontmatter itself — a model never gets to invent YAML keys or a name that disagrees with its own directory. You can also just let it happen. A session about to be compacted is asked to write down both what it learned and what it worked out, which is the moment where not having done so costs immediately. Read the queue shell Copy synapse skill proposed One row per skill nobody has looked at, oldest first, with the tool that wrote it and the line it left saying why. Read the thing itself before deciding: shell Copy synapse skill show cut-a-release Start a new session in the same project and you will be told the count once, at the boundary, and never again — Synapse connected · 1 skill to review . Waiting is what a proposal is for; a queue that interrupts is not a queue. Prove the gate for yourself Do not take it on faith. Look where the skill would be if it had been installed: shell Copy ls ~/.claude/skills/ synapse skill status cut-a-release It is not there, and the status says waiting for review rather than not installed — a different fact with a different fix. Now try the blunt instrument: shell Copy synapse skill install ls ~/.claude/skills/ Still not there. Installing everything means everything approved ; a proposal reaching a tool because somebody ran a bulk install is the one way this gate could leak, so both the CLI and the app filter it explicitly. Why the gate is on installing, not writing The library is Synapse's own folder, so writing there costs nobody anything. A skill's description is loaded into every session of every tool holding it — that is the bill you have to agree to. So teaching is free and installing is the decision, rather than the other way around. Approve one shell Copy synapse skill approve cut-a-release synapse skill status cut-a-release Now it is installed, and the queue is empty. If the agent scoped it to the project, it went into that repository's own skills folder rather than your home — a procedure about one checkout belongs to it. Turning one down removes it and its history: shell Copy synapse skill reject some-other-skill --confirm Only something still waiting can be rejected. Once approved it is an ordinary skill, and skill delete is what removes it — a command that says what it does. Let it correct itself Use the skill in a session and let it turn out wrong — a missing step, a stale path. Tell the session: text Copy Step 3 is wrong now. Fix the skill. It calls revise , and this one does reach the copies Synapse installed: shell Copy grep -c "the corrected step" ~/.claude/skills/cut-a-release/SKILL.md That is the deliberate exception. You already agreed to this skill being loaded, and a correction that never arrives leaves every session running the version that was wrong. It reaches only copies Synapse wrote and nobody has edited — one you changed by hand is yours, and is left where it is. Take a correction back shell Copy synapse skill history cut-a-release synapse skill revert cut-a-release The history is every version the skill has had, newest first, with the line saying what was wrong with each. Reverting puts one back in the library and in every tool holding a Synapse copy — and is itself recorded as a revision, so a revert can be reverted. The newest twenty are kept. This is the same bargain a corrected memory makes: nothing is hidden without a way back. Clean up shell Copy synapse skill remove cut-a-release synapse skill delete cut-a-release --confirm synapse settings learn off Turning it off leaves everything already in the library exactly where it is. What changes is that no new session gets the tools. What to read next Curate and optimize memory is the same idea for the other half of what a session learns — facts rather than procedures. Hand a job to one agent gives you a session long enough to work out a procedure worth keeping."},{"title":"Start a tool with everything in place","description":"Use synapse launch to open Codex, Claude Code, or pi with memory, scoped credentials, and the project root already wired, without writing anything into that tool's own configuration.","path":"tutorials/launch/","text":"Outcome and prerequisites You will open a coding tool that has memory, the folder's scoped credentials, and the right project root from its first turn — and confirm that your machine is exactly as it was afterwards. synapse launch wires a tool for the life of one process. It is not a setup step and it writes nothing into the tool's own configuration. The synapse CLI installed and on PATH . Codex, Claude Code, or pi installed. None of them needs to be connected — that is the point of this tutorial. A scratch project folder. Steps four and five create a disposable vault value. Launching is not connecting synapse connect is a decision you make once: it registers the MCP server in the tool's own configuration and leaves it there. synapse launch makes no such change. If the tool has no connection of its own, Synapse hands it a generated configuration for that one process and the machine is unchanged when it exits. Preview before you launch Work in a scratch folder so nothing here touches a real project: shell Copy mkdir -p "$HOME/tmp/launchtutorial" && cd "$HOME/tmp/launchtutorial" git init --quiet . synapse launch claude --print --print resolves everything and shows you the result instead of running it: text Copy /Users/example/.local/bin/claude --mcp-config ~/Library/Application Support/synapse/relay/launch.66c4a25fcfe8.mcp.json env SYNAPSE_PROJECT_DIR=/Users/example/tmp/launchtutorial Try Codex too. It reads no MCP configuration file, so it gets the server on its command line instead: shell Copy synapse launch codex --print text Copy /Users/example/.asdf/shims/codex -c mcp_servers.synapse.command="/Users/example/.local/bin/synapse" -c mcp_servers.synapse.args=["mcp"] env SYNAPSE_PROJECT_DIR=/Users/example/tmp/launchtutorial Read what it actually did Three things were resolved, and each is worth understanding before you trust the command with a real project. Piece Where it came from The program Looked up in the registry of connectable tools, then on PATH . A tool that is not installed fails here with a name, not a shell error. The MCP wiring Written only because this tool has no Synapse connection of its own. A connected tool is launched as-is and gets no generated file at all. SYNAPSE_PROJECT_DIR The folder you launched from, walked up to its project root. This is what scopes the tool's memory and, on the mesh, its registration. The generated configuration is keyed on a digest of the project root, so relaunching in the same folder reuses one file rather than accumulating them. Look at it if you like — it names this binary and the mcp argument, and nothing else. Why this exists at all Before launch , wiring a tool meant either connecting it permanently or assembling the arguments by hand. The first is a commitment you might not want on a machine you are borrowing; the second is easy to get subtly wrong. launch works before setup has ever run and leaves nothing behind. Add scoped credentials A launched tool can run a shell, so it gets the same scoped environment synapse run would give a child. Create a disposable value and map it to this folder: shell Copy synapse vault create demo synapse secret set demo token DEMO_TOKEN Enter a throwaway value at the hidden prompt. Then create the scope and approve it: shell Copy synapse scope init . yaml Copy version: 1 scope: project env: DEMO_TOKEN: demo.token deny: [] shell Copy synapse allow synapse launch claude --print The preview now names the variable and refuses to show what is in it: text Copy /Users/example/.local/bin/claude --mcp-config …/launch.66c4a25fcfe8.mcp.json env SYNAPSE_PROJECT_DIR=/Users/example/tmp/launchtutorial env DEMO_TOKEN=<from the vault> <from the vault> is not a redaction applied to a value that was read. A preview calls a different code path that lists names and never opens the vault at all, so there is no value in the process to leak. Watch it refuse a half-resolved environment Break the scope's approval by editing the file — a blank line is enough, because approval is bound to the exact bytes: shell Copy echo "" >> .synapse.yaml synapse status . text Copy Folder: . Available: none Ambient: blocked /Users/example/tmp/launchtutorial/.synapse.yaml [project · pending] warning: /Users/example/tmp/launchtutorial/.synapse.yaml: Scope has not been approved Now try to launch: shell Copy synapse launch claude --print text Copy Error: vault scope is not ready: /Users/example/tmp/launchtutorial/.synapse.yaml: Scope has not been approved It refuses rather than starting the tool with DEMO_TOKEN missing. That is deliberate and it is the same rule synapse run applies: a tool that can run a shell is never handed a partly-resolved environment, because the failure mode is a command that runs against the wrong thing and looks like it worked. Read the changed file, then approve it again: shell Copy synapse scope status . synapse allow synapse launch claude --print Pass the tool its own flags Everything after a bare -- reaches the tool untouched: shell Copy synapse launch claude --print -- --resume --model opus text Copy /Users/example/.local/bin/claude --mcp-config …/launch.66c4a25fcfe8.mcp.json --resume --model opus env SYNAPSE_PROJECT_DIR=/Users/example/tmp/launchtutorial env DEMO_TOKEN=<from the vault> The split happens before Synapse parses anything, which is why a flag both programs understand still reaches the right one. Without that, a --model meant for the tool would be eaten by Synapse. Launch for real shell Copy synapse launch claude The tool opens as it normally would. Ask it what Synapse tools it has; the answer should include remember , recall , readmemory , and vaultstatus . Ask it to call vaultstatus and it will report DEMO_TOKEN as available — the name only, because that tool returns metadata and cannot read the value. If this is Claude Code and it is also connected, its first message will carry this project's memory; see Session start . A launched tool that is not connected has the MCP tools but not the session hook, because the hook lives in that tool's own settings and launching writes nothing there. Exit the tool. Then confirm the machine is unchanged: shell Copy synapse status The tool's own configuration was never opened. Nothing needs undoing. Clean up shell Copy synapse deny synapse secret forget demo.token synapse vault delete demo cd .. && rm -rf launchtutorial Forgetting the secret removes the stored value and Synapse's record of it. The generated MCP configuration under the data folder is harmless — it names this binary and nothing else — and is overwritten on the next launch in the same folder. What you can rely on Launching never edits the tool's own configuration. Making a connection permanent is synapse connect , a separate decision. A preview prints variable names and never a value, because it never reads one. Any scope warning refuses the launch outright rather than starting the tool with part of its environment. A connected tool is launched as-is. The generated configuration exists only for a tool that has no connection of its own. Next step Continue to Run a team of agents and drive it yourself , which uses the same launch pipeline to open several tools at once and put you on the roster with them."},{"title":"Run a team of agents and drive it yourself","description":"Turn on the agent mesh, open a team, watch what every agent is doing from one terminal, answer a worker that gets stuck, and shut the whole thing down cleanly.","path":"tutorials/mesh/","text":"Outcome and prerequisites You will put several coding-agent sessions on one local mesh, give them work from your own seat rather than through a lead agent, read what each one is doing without opening its terminal, and answer one that gets stuck. Then you will take it all down and confirm nothing is left running. The synapse CLI installed, and at least one tool connected. Start with Install and connect your first tools if not. A real project folder. Agents work on real files, so use a checkout you can throw away or one with clean version control. Thirty to sixty minutes, and a willingness to let several agent sessions run at once. These are real agents on your real machine A background worker runs with its own tool's permission prompts bypassed, because there is no terminal in which to answer one. Everything below happens on your actual files. Use a repository whose state you can restore. Turn the mesh on The mesh ships switched off. Its sixteen tools load into every connected session, and that costs context in each one, so Synapse does not add them to a setup that will not use them. shell Copy synapse relay status text Copy Mesh: off Agents: 0 online of 0 Workers: 0 Turn it on with `synapse settings mesh on`. shell Copy synapse settings mesh on text Copy Agent mesh on. Connected tools pick this up the next time they start. That last sentence is literal. A session already running keeps the tool list it started with, so restart any open tool before continuing. The same applies when you switch the mesh back off. Turning the mesh on also brings the guidance that explains it. The tools and the instructions for using them appear together and are withdrawn together, so a session can never have one without the other. Look at roles and teams A role is a durable identity: a brief describing what an agent owns and how it coordinates. A team is a named roster of roles. Both ship with sensible defaults: shell Copy synapse relay role list text Copy backend built-in devops built-in frontend built-in qa built-in reviewer built-in supervisor built-in worker built-in shell Copy synapse relay team list synapse relay team show web text Copy # web · built-in name = "web" [[member]] name = "lead" role = "supervisor" [[member]] name = "frontend" role = "frontend" [[member]] name = "backend" role = "backend" [[member]] name = "reviewer" role = "reviewer" Read one role to see what an agent is actually told: shell Copy synapse relay role show reviewer text Copy # reviewer · built-in name = "reviewer" description = """ You review other agents' work. Read the diffs they report, check correctness, edge cases, and conventions, and send concise, actionable feedback to the author. Approve only when it holds up; escalate disagreements to the supervisor. """ Roles resolve from the project first, then your own layer, then the built-ins. A role saved into a project lives in .synapse/roles/ and travels with the checkout, which is how a team convention becomes something the repository carries rather than something each person configures. Join as yourself, not through a lead There are two ways to run a team, and the difference matters. synapse relay team open web launches every member and puts a lead agent in your terminal. You brief the lead, the lead hands out tasks and relays answers back. That costs the lead's context on every message and makes it a bottleneck for work it is not doing. synapse mux puts you on the roster instead, with the same messaging every agent has: shell Copy cd ~/your-project synapse mux --team pair Synapse launches the team's members in the background and drops you into a line-oriented prompt with your own name on the roster. It is deliberately plain — no terminal library, no full-screen interface — so it works over ssh and in any terminal. text Copy @backend the created column needs a default, not a backfill #build freezing the schema in ten minutes ! stop and report where you are /focus backend and the index too Syntax Goes to @name text One agent, directly. #channel text Every subscriber of that channel. ! text Everyone on the mesh. text Whoever is focused, so a back-and-forth reads like a conversation. /help lists the rest. The ones you will use are /agents , /workers , /focus <name> , /log <name> , /kill <name> , and /quit . Watch what everyone is doing From inside the mux, /agents shows the roster. From another terminal, the same thing: shell Copy synapse relay agents text Copy you — you — — /work/api lead supervisor online working splitting the migration into three tasks /work/api backend backend online blocked need the staging database name /work/api frontend frontend online working rewriting the auth middleware /work/api The fourth column is a state and the fifth is the note that goes with it. The state tells you an agent has not stalled; the note is the part that tells you whether to leave it alone. For a headless worker there is no terminal to look at, so that note is the only view of it there is. Notes are one line, bounded, and kept when a later report does not carry one — an agent that reports working twice has not stopped doing the thing it described the first time. shell Copy synapse relay status # counts and whether the mesh is on synapse relay channels # channels and how many subscribe to each synapse relay feed --follow # every message between agents, as it happens Every one of these takes --json . The Mesh page in the desktop app shows the same information with the traffic alongside it. Notes are guidance, not a guarantee Writing one is something connected tools are told to do, not something Synapse can enforce inside another tool's session. A stale note is possible; the roster's own liveness is what tells you whether the agent is still there, and an agent that stops answering leaves the roster within about a minute and a half. Understand the wait loop This is the one piece of the mesh worth understanding properly, because almost every confusing behavior traces back to it. An idle agent calls wait , which blocks until a message arrives for it. After a few idle minutes it returns an empty list instead. That is a normal timeout, and the agent's instructions are to call wait again. An idle teammate therefore costs one tool call every few minutes rather than a loop that spins. Two consequences you will actually notice: Messages arrive at the next check, not as an interrupt. An agent parked between tasks answers in about a second. One in the middle of a long build sees you when it comes back. The roster's state column tells you which is which. An empty or failed wait is not a signal to stop. Agents are told this explicitly, in the guidance and again in the launch harness, because an agent that reads a timeout as \"the work must be finished\" writes an explanation and exits. That is the single most common way a mesh session dies. Delivery is at least once. A reply lost in flight is delivered again rather than dropped, so a duplicate is possible on that rare path and a lost message needs a process to die inside a very small window. Grow the team while it works You do not have to decide the roster up front. A supervisor can spawn workers as it discovers what the job needs, and you can do the same from the command line: shell Copy synapse relay launch migrations --role backend --task "write the schema migration" synapse relay ps synapse relay kill migrations A worker started this way runs headless, registers itself, and parks on wait until it is given something to do. From inside the mux, /workers lists them and /log <name> shows what one has actually been doing. Workers belong to the session that started them, for exactly as long as that session lives. There is no daemon: closing the mux takes its workers with it, so nothing is left running behind you. A worker that exits is restarted with a growing backoff, and one that never manages a healthy run is retired rather than restarted forever. At most eight run at once. Answer a worker that gets stuck This is the reason to be on the roster yourself rather than behind a lead. A headless worker runs with its permission prompts bypassed, so until there is a person on the mesh it has nobody to ask when it reaches a decision it should not make alone — and its only option is to guess. With you on the roster it can send you the question, report itself blocked with a note saying what it needs, and wait. text Copy backend: the migration drops a column with data in it. Confirm before I run it? /focus backend yes, it is a duplicate of created_at — go ahead Agents can tell a person from an agent. Your roster row is marked as a human, and connected tools are told to ask you questions and never delegate work to you. Only synapse mux can set that flag; a tool calling register is always an agent, whoever is sitting in front of it. Write a role of your own The built-ins are a starting point. Create one in the project so it travels with the checkout: shell Copy synapse relay role create migrator toml Copy channels = ["build"] tool = "claude" # model = "claude-opus-5" # driver = true # stay interactive instead of parking # tools = ["Read", "Edit", "Bash(git:*)"] # pre-granted tool rules description = """ You own database migrations. Write them forward-only, never destructive without asking, and post the SQL to #build for review before running anything. """ Editing a built-in copies it down into a layer you own rather than modifying the shipped template. --user writes into your own layer instead of the project. Then build a team around it: shell Copy synapse relay team create schemawork synapse mux --team schemawork Shut it down shell Copy # from inside the mux /quit # then, from any terminal synapse relay ps # should be empty synapse relay agents # names age off within about ninety seconds synapse settings mesh off Leaving the mux takes its workers with it and takes your name off the roster, so nothing addresses an empty terminal. Switching the mesh off removes the sixteen tools and their guidance from every session that starts afterwards; sessions already running keep what they had until they restart. Messages stay in the local database and remain visible through synapse relay feed . They are memory-adjacent, not memory: they are never recalled and never returned by recall . What you can rely on No daemon, no port, no token. The bus is the same local database as your memory, and a message between two agents never leaves your Mac. A message from another agent is information, not instruction. Connected tools are told to treat mesh traffic as untrusted input that never overrides you, your shared guidance, or repository rules. A person on the mesh is addressed, never assigned. Only the mux can mark a roster row as human. Channel and broadcast history is not replayed — a new agent sees only what is sent after it joins, so start workers before briefing them. A direct message is held for an agent that has not registered yet. Workers die with the session that started them. A session that closes takes its name off the roster with it. Next step You have finished the team operator track. The maintainer track covers keeping the store healthy and leaving cleanly: Export and restore safely , then Check, migrate, and remove Synapse ."},{"title":"Hand a job to one agent and watch it grow a team","description":"Start a single overseer, describe an outcome instead of picking a roster, answer it when it asks, and see why nothing sits between you and the workers it starts.","path":"tutorials/overseer/","text":"Outcome and prerequisites You will start one agent, give it a goal rather than a task list, and let it decide whether the work needs a team at all. Along the way you will answer a question it cannot answer itself, and message a worker it started without going through it — which is the difference between this and every other way of running agents. The synapse CLI installed and on PATH , with the mesh on: synapse settings mesh on . Claude Code installed and signed in. The agents run under whatever account you already have — Synapse holds no model credential and makes no model call. A checkout you can afford to have edited. Agents here touch real files. About thirty minutes. If the mesh is new to you, read Run a team of agents and drive it yourself first. This tutorial is the same machinery with one agent instead of four. Start one agent, not a roster The other teams ask you to choose a shape before you understand the job. overseer is one member: shell Copy cd ~/code/your-project synapse relay team show overseer synapse mux --team overseer Two things happened. An agent started in the background under the overseer role, and you joined the mesh under your login name. Check both: shell Copy /agents You should see two rows: yourself, marked as a person, and overseer . The mark matters — every agent on the mesh is told to ask a human row questions and never delegate work to one. Describe an outcome Say what you want to be true, not which steps to run: text Copy @overseer the release notes for the next version are missing. Work out what changed since the last tag and write them. Watch what it does before it does anything: shell Copy /agents Its note is the interesting part. An overseer's brief tells it to weigh doing the work itself against splitting it, because every worker it starts is a separate session on the same account you are paying for. A small job stays one agent. A job with two genuinely parallel halves gets a second. Spawning is not free, and it knows that The role says so in as many words: spawn for work two agents can do at the same time, not to look busy, and stop one whose piece is finished rather than leaving it idling. Answer it when it asks Sooner or later it will reach something only you can settle — which version number, whether a change is worth mentioning, whether to touch a file it is unsure about. It will send you the question and report itself blocked: shell Copy /agents A row reading blocked with a note is an agent waiting on you. Reply to it like anything else: text Copy @overseer call it 2.4.0, and leave the vendored files out entirely This is the whole reason a person belongs on the roster. A headless worker runs with its permission prompts bypassed, so without somebody to address, its only option at a fork is to guess. Reach past it If the overseer started workers, they are on the roster too — and you can address them directly: shell Copy /agents /log notes text Copy @notes skip the dependency bumps, they are noise Nothing was relayed. You did not interrupt the overseer's context to correct one of its workers, and it did not have to spend a turn passing the message along. Why that matters more than it sounds A lead that relays for you pays its own context on every message and becomes a bottleneck for a job it is not doing. The overseer is addressable, never interposed — which is the same reason synapse mux exists at all. Bound the fan-out How many agents a machine can usefully carry is a fact about the machine: shell Copy synapse settings workers 4 synapse settings show | grep workers A supervisor already running picks this up on its next spawn. The number is clamped to a ceiling in code as well as refused on the way in, so a mistyped digit cannot buy an unbounded fleet — the setting is a preference, and the ceiling is the promise. Clean up shell Copy /workers /kill notes /quit Leaving takes the workers it started with it, and drops your roster row so no agent goes on addressing questions to somebody who is not there. What to read next Run a team of agents and drive it yourself is the same mesh with a roster you choose. Let your agents write skills turns a procedure one of these sessions worked out into something the next one starts with."},{"title":"Export and restore safely","description":"Create a validated snapshot, make one reversible change, stop every database user, restore, and prove the previous state came back — including what a snapshot deliberately does not carry.","path":"tutorials/recovery/","text":"Outcome and prerequisites You will take a consistent snapshot of the whole store, make a change you can see, restore over it, and prove the change is gone. Then you will understand exactly what a snapshot contains, what it deliberately leaves out, and how that affects moving to another machine. The synapse CLI installed. A destination path that does not already exist. A terminal you can keep open, to record one memory ID. This really does replace the active database The tutorial is designed to be reversible and Synapse writes a pre-restore snapshot of its own before it does anything. But restore is a genuine database replacement. Read each step before running it, and do not substitute a production backup you have not separately preserved. Check the store before you trust a copy of it shell Copy synapse path synapse data check text Copy Database: /Users/example/Library/Application Support/synapse/brain.db Version: 7 Integrity: ok Integrity must read ok . Two separate checks stand behind that word: a relationship check that runs every time the database opens, and a full page-by-page scan that reads the whole file. Exporting a store that fails either one copies the problem rather than saving you from it. If it reports anything else, stop here and follow the corruption guidance in Troubleshooting before continuing. Create a fresh export shell Copy backup="$HOME/Desktop/synapsetutorialbackup.db" test ! -e "$backup" synapse data export "$backup" text Copy Exported /Users/example/Desktop/synapsetutorialbackup.db That one line hides four steps. Synapse writes a consistent compact snapshot rather than copying a file that may be mid-write, secures its permissions to owner-only, reopens it read-only, and validates it — all before reporting success. An export that would not open is a failure here rather than a surprise months later. The snapshot is a complete SQLite database. It holds every memory with its scope and project, vault and secret metadata , scope trust records, import batches, skill install receipts, mesh history, and your settings. Add one marker after the export shell Copy printf '%s\\n' 'This entry exists only after the tutorial export.' \\ | synapse memory add synapserecovery synapse memory list synapserecovery text Copy Stored memory #42 Record that ID. It is the marker that must disappear when the restore succeeds — which is what turns this from \"the command did not error\" into an actual proof. Stop every database user Quit the Synapse desktop app. Close every connected session so its synapse mcp child process exits. A plain CLI command releases its lock when it finishes, so nothing needs doing about those. Reading the database takes a shared lock; restoring requires an exclusive one. If you deliberately leave a session open, the next step will refuse and tell you to close the app and connected tools. Do not work around the lock The refusal is not a formality. Replacing a database file underneath a process that has it open is how a store gets corrupted in a way no integrity check can undo. If restore refuses, find the process that is still holding it. Restore shell Copy synapse data restore "$backup" text Copy Restored /Users/example/Desktop/synapsetutorialbackup.db Previous database: /Users/example/Library/Application Support/synapse/backups/brain.1785776950101.restore.db Read the second line. Before replacing anything, Synapse wrote the current database into the backups folder with a restore marker in its name. If you have just restored the wrong file, that path is your way back — and it is printed rather than left for you to discover. The sequence is: validate the source, validate the current database, acquire the exclusive lock, snapshot the current state, then replace atomically. Any step failing leaves the original in place. Verify the earlier state actually returned shell Copy synapse data check synapse memory list synapserecovery Integrity should read ok , and the marker must be absent — an empty result is the success condition. Confirm something you expect to still be there as well, so you are testing that the right state returned rather than that the database is merely empty: shell Copy synapse memory list Reopen the desktop app and your connected tools only after this verification passes. What a snapshot does not carry This is the part that surprises people, and it follows directly from the security model. Not in the export Why, and what to do Secret values They live in their own store and were never in brain.db . On the same machine, restored metadata still points at them and everything works. On another machine, recreate each with synapse secret set — or, on the encrypted store, carry vault.db and vault.key across together, since neither is any use without the other. Your SOUL.md A file beside the database, not a table inside it. Copy it separately if it matters — and it usually does. Your skill library Directories under the data folder. Copy skills/ separately. Roles and teams TOML under the data folder, plus anything in a project's own .synapse/roles/ , which travels with that checkout instead. Tool configuration Belongs to the tools themselves. Reconnect on the new machine rather than copying their files. shell Copy # a complete manual copy, when the database alone is not enough cp -R ~/Library/Application\\ Support/synapse ~/Desktop/synapse-full-copy Backups you did not ask for Synapse writes snapshots on its own at two moments: before any schema migration, and before any restore. Both land in the same place: shell Copy ls ~/Library/Application\\ Support/synapse/backups/ Only the newest few are kept, so the folder cannot grow without limit — the same rule that bounds worker logs and the crash log. That means an automatic backup is a safety net for the operation that just happened, not an archive. If you want a copy to keep, take an explicit export and move it somewhere Synapse does not manage. Moving to a new Mac On the old machine Run synapse data export , and copy SOUL.md and the skills/ directory alongside it. Note which secrets exist with synapse secret list for each vault — the names, since you cannot export the values. On the new machine Install Synapse, install the CLI, then restore the export before connecting any tools. Put SOUL.md and skills/ back in the data folder. Then Recreate each secret with synapse secret set , re-approve each project scope with synapse allow — approval is per machine, because it is a statement about a file you have read — connect your tools, and run synapse skill install . shell Copy synapse doctor One command to confirm the new machine matches the old one: same schema version, same memory count, tools connected, skills installed. Next step Finish the maintainer level with Check, migrate, and remove Synapse , which covers the health report in full and both removal paths."},{"title":"Check, migrate, and remove Synapse","description":"Read a full health report, understand what a schema migration does to your store, disconnect one tool, and remove everything Synapse installed while keeping your memory and your own files.","path":"tutorials/lifecycle/","text":"Outcome and prerequisites You will produce the report to attach to a bug, verify the store's integrity and schema version, understand what happens when a release changes the schema, and then walk both removal paths — one tool, and everything — reading what each will do before it does it. The synapse CLI installed and at least one tool connected. Export and restore safely read first. This tutorial assumes you know how to take a snapshot; the removal steps are more comfortable when you have one. Read before you confirm Every destructive command here refuses to act without --confirm and prints what it would do instead. That preview is the feature. Run each command once without the flag and actually read the output before adding it. Get the whole picture in one command shell Copy synapse doctor text Copy Synapse 0.1.0-beta.24 Store State ok Schema v10 Memories 0 Size 0 MB Backups 0 Recall budget balanced Connected tools Claude Code installed, not connected version 2.1.241 (Claude Code) guidance no notice no compaction no status line none Codex installed, not connected version codex-cli 0.147.0 guidance no notice no compaction no status line none pi not installed Skills In the library 1 Installed 0 Out of date 0 Agents write no This is what to attach to a bug report. It answers most first questions without a round trip: which version, whether the store is sound, what schema it is on, whether each tool is actually connected rather than merely installed, and whether the guidance pointer, session notice, and status line are in place. --json gives the same thing structured. Note what is not here: no memory contents, no secret names, no file paths outside Synapse's own. A doctor report is safe to paste into a public issue. Read the tool rows carefully. installed, not connected means the tool is on the machine but has no Synapse entry. Agents write under Skills is whether self-improvement is on, and a count of anything waiting for you appears under it when there is any. A tool that was connected and whose executable later moved reports as stale rather than healthy, because detection resolves the stored command rather than trusting the entry exists. Check the store itself shell Copy synapse data check synapse data check --json text Copy { "path": "~/Library/Application Support/synapse/brain.db", "version": 7, "integrity": "ok" } Two different checks run when the database opens, with very different costs. A relationship check is index-driven and runs every single time. A full page-by-page scan reads the whole file, so it runs once per process against an unchanged file — which is why a status line redrawing on every turn stays cheap while data check is thorough. integrity: ok means both passed. Anything else is reported rather than worked around, and restoring a snapshot is the answer. Understand what a migration does Schema v7 in the report above is the store's version. Each release that changes the schema adds a numbered migration and raises that number. Opening the database applies any that are missing, in order. Three things are true of every migration and worth knowing before you upgrade: A backup is taken first. Before any migration runs against an existing store, Synapse copies it into the backups folder. If a migration fails, that copy is what you restore. Shipped migrations are never edited. A release only ever appends. That is what makes the upgrade path from any older version deterministic rather than dependent on which releases you happened to install. A newer store refuses an older binary. If the database reports a version this release does not know about, Synapse says so and stops rather than guessing at a schema from the future. shell Copy ls ~/Library/Application\\ Support/synapse/backups/ Backups are bounded — only the newest few are kept — so the folder cannot grow without limit. The same rule applies everywhere Synapse writes something unbounded: worker logs and the crash log keep their tail rather than the whole history. Upgrading is just opening it There is no migrate command and nothing to run by hand. Install a new release, run any synapse command or open the app, and the store is brought forward with a backup beside it. synapse doctor afterwards will show the new schema number. Disconnect one tool Disconnecting undoes one tool's integration and nothing else: shell Copy synapse disconnect claude text Copy Removed Claude Code skill `synapse-mesh` warning: Claude Code skill `release-checklist`: `release-checklist` in Claude Code has been changed since Synapse installed it Read that second line closely, because it is the whole design in one sentence. Synapse removed the skill it had written and left untouched. It refused to remove the one that had been edited since — because at that point the file is partly yours, and Synapse will not delete work it cannot prove is its own. The same run also asks the tool's own CLI to forget the MCP server, strips the managed block from its instruction file while leaving your text in place, and removes the session hook and status line from its settings — but only if they are the ones Synapse wrote. A status line you configured yourself is reported and left alone. Every step reports rather than aborts. A disconnect that stopped at the first problem would leave the tool half-connected, which is the worst available outcome; finishing and telling you what it could not do is better. shell Copy synapse disconnect With no tool named, it disconnects every one. Read the uninstall preview Run it without --confirm first. It always previews: shell Copy synapse uninstall text Copy `synapse uninstall --confirm` would remove: · every skill Synapse installed, leaving any you wrote Your memory in ~/Library/Application Support/synapse would be left alone. Add --confirm to go ahead. On a fully connected machine the list is longer — the MCP entries, the managed instruction blocks, the session hook, the status line, the installed CLI, the shell startup block. The last line is the one to notice: memory is never removed as a side effect. Uninstalling the software does not delete what you have taught it. Remove everything, including memory Taking the data folder requires asking for it by name, and the preview changes to say so: shell Copy synapse uninstall --data text Copy `synapse uninstall --confirm` would remove: · every skill Synapse installed, leaving any you wrote And, because --data was given, everything in ~/Library/Application Support/synapse including all of your memory. That cannot be undone. Add --confirm to go ahead. Two flags, both required, and a sentence that says it cannot be undone. If you want a copy first, synapse data export writes a consistent snapshot you can restore into a fresh install later — see Export and restore safely . shell Copy synapse data export ~/synapse-final-snapshot.db # optional, but do it synapse uninstall --data --confirm Check where your values live first On the encrypted store, vault.db and vault.key are in the data folder, so --data deletes every secret value with it. The command says so before it acts. On the Keychain store the values are outside the folder and survive: removing it costs Synapse the ability to find them, so use synapse secret forget for those first. synapse vault backend says which store this machine uses. What survives The principle behind all of this: everything Synapse writes outside its own folder, it can take back — and nothing else. After a full uninstall, these are untouched: Thing Why it survives Your own words in CLAUDE.md or AGENTS.md Only the managed block between markers is removed. Text outside it was never Synapse's. A skill you wrote or edited Removal requires an install record proving Synapse wrote that exact content. A status line somebody else configured JSON has no comments, so entries are recognized by the command they run. One that is not Synapse's is reported, never replaced. Other MCP servers in either tool Removal goes through the tool's own CLI, by name, for the synapse entry only. Your memory, unless --data Memory is never removed as a side effect of removing software. Secret values on the Keychain store They were never in the data folder, so removing it cannot delete them. On the encrypted store they are in that folder, and --data takes them with it. A project's .synapse.yaml and .synapse/roles/ They belong to the checkout, not to your machine. Next step That is the whole maintainer track. If something in it did not behave as described, synapse doctor --json plus the exact command you ran is everything a bug report needs. See Troubleshooting for the common failures and what each one means."}] \ No newline at end of file diff --git a/website/docs/cli.ts b/website/docs/cli.ts index 44de89d..22cc5d8 100644 --- a/website/docs/cli.ts +++ b/website/docs/cli.ts @@ -158,7 +158,7 @@ ${command("guidance adopt", "synapse guidance adopt --confirm", "Move unmanaged

Installation and paths

${command("install", "synapse install", "Install the current executable for this user. A packaged app creates a launcher into the signed bundle; a development binary is copied atomically. Unrelated destination files are never overwritten.")} ${command("path", "synapse path", "Print the resolved data directory, SOUL.md, and CLI destination.")} - ${command("savings", "synapse savings [--days <n>|--all] [--json]", "Tokens and dollars Synapse kept out of your sessions, compared with keeping the same memory in an instruction file every session loads. Counts what each session could reach, what recall and the session hook actually sent, and what Synapse's own tool definitions and guidance cost. Defaults to the last 30 days. Figures are characters divided by four, counted once per session, so they understate.")} + ${command("savings", "synapse savings [--days <n>|--all] [--json]", "Tokens and dollars Synapse kept out of your sessions, compared with keeping the same memory in an instruction file every session loads. Counts what each session could reach, what recall and the session hook actually sent, and what Synapse's own tool definitions and guidance cost. Defaults to the last 30 days. Usage older than 730 days is pruned as new activity is recorded. Figures are characters divided by four, counted once per session, so they understate.")} ${command("doctor", "synapse doctor [--json]", "Report everything a bug report needs: version, store state and size, drift found from the current folder, connected tools and what each is set up with, skill and mesh state, shell and CLI integration, resolved paths, and recent crashes. Every check reports rather than fails, so a broken store is described instead of stopping the report. Nothing is sent anywhere.")} ${command("connect", "synapse connect [tool]", "Wire a tool into memory and the vault: register the Synapse MCP server through that tool's own CLI, and point its global instruction file at SOUL.md. With no name it connects every tool this machine has. An existing registration that already points at Synapse is left alone. Synapse never edits a tool's configuration itself \u2014 it asks the tool to.")} ${command("connect --refresh", "synapse connect <tool> --refresh", "Apply this release's descriptor to a tool that is already connected, writing its registration again. Detection cannot tell a descriptor that moved from one that did not, so a tool set up under an older release keeps that release's answer until this is run. Nothing is removed and the tool stays connected throughout. synapse doctor reports update available when it is worth running.")} diff --git a/website/docs/install.ts b/website/docs/install.ts index fe47be8..07e45e1 100644 --- a/website/docs/install.ts +++ b/website/docs/install.ts @@ -5,12 +5,13 @@ import { releaseurl } from "../deploy"; export const install: Page = { path: "docs/install/index.html", title: "Install and connect", - description: "Install the macOS desktop app or build the Linux CLI, then connect your coding tools.", + description: "Install Synapse on macOS or Windows, or build the Linux CLI, then connect your coding tools.", kind: "docs", toc: [ { label: "Requirements", id: "requirements" }, { label: "Linux setup", id: "linux" }, { label: "macOS app", id: "app" }, + { label: "Windows installer", id: "windows" }, { label: "Install the CLI", id: "cli" }, { label: "Shell integration", id: "shell" }, { label: "Connect tools", id: "tools" }, @@ -22,13 +23,14 @@ export const install: Page = {

Requirements

  • macOS desktop: Apple-silicon Mac running macOS 13 or later.
  • +
  • Windows desktop: a Windows x64 machine with Direct3D 11.
  • Linux CLI and MCP server: a current stable Rust toolchain and a C compiler/linker to build from source.
  • At least one supported tool on PATH: Codex, Claude Code, pi, or Ainz.
  • A writable ~/.local/bin, or a custom path supplied through SYNAPSE_BIN.

Install on Linux

-

Linux supports the CLI and MCP server through synapsecore. This build does not depend on the desktop UI. Current release downloads contain the macOS app; build the Linux version from the source repository.

+

Linux supports the CLI and MCP server through synapsecore. This build does not depend on the desktop UI. Release downloads contain the macOS app and Windows installer; build the Linux version from the source repository.

From the repository root, run:

${code("shell", `cargo build --release --locked --manifest-path crates/synapsecore/Cargo.toml ./crates/synapsecore/target/release/synapse-cli install @@ -46,6 +48,9 @@ synapse status`)} ${note("Why move it first?", "The installed CLI is a small launcher that points into the signed application bundle. Moving the app later would leave that launcher pointing at the old location.")} +

Install on Windows

+

Download the synapse.<version>.windows.x64.exe installer from the latest release. It installs the app and CLI for your user account. Open a new terminal after installation to use synapse by name. See the Windows guide for build and verification steps.

+

Install the CLI

Open Settings in Synapse and choose Install CLI, or run the application binary once from a terminal and use:

${code("shell", `synapse install diff --git a/website/home.ts b/website/home.ts index 030902c..82b1228 100644 --- a/website/home.ts +++ b/website/home.ts @@ -27,7 +27,7 @@ const body = ` Download for macOS Try a handoff
- macOS desktop · Linux terminal, CLI & MCP · Linux setup + macOS and Windows desktop · Linux CLI & MCP · Install options
@@ -133,7 +133,7 @@ Settings → Shell integration → Enable
@@ -154,7 +154,7 @@ Settings → Shell integration → Enable export const home: Page = { path: "index.html", title: "Synapse", - description: "Share local project memory and skills between Claude Code, Codex, pi, and Ainz. Recall saved decisions across coding agents. Open source for macOS and Linux.", + description: "Share local project memory and skills between Claude Code, Codex, pi, and Ainz. Recall saved decisions across coding agents. Open source for macOS, Windows, and Linux.", kind: "home", body, contract,