Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 27 additions & 3 deletions packages/agent-memory-sync/profiles/linux.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,32 @@
"frictions producer: friction-log sync_export in",
"~/.config/friction-log/config.yml writes ~/.harness/frictions/linux.json.",
"",
"Both the machine-state and frictions entries set \"ownerScoped\": true —",
"the owner-writes-only convention above is now enforced, not just",
"documented: push only ever offers this machine's own <profile>.json",
"(machine-state/linux.json, frictions/linux.json), never a peer's file",
"this machine merely pulled. See collectLocalSyncFiles' ownerFilter",
"option in src/memory-sync/config.ts and",
".ai/runs/2026-08-03-sync-conflict-markers-echo/03-decisions.md (D-002 to",
"D-004) for the echo/last-writer-wins race this closes. Pull is",
"unaffected — a peer's file is still materialized locally as before.",
"",
"The 'profile' field ('linux' below) is NOT a cosmetic label, despite",
"what linux.example.json's template comment (referenced above) may",
"suggest — corrected post Fix-Runde agent-tasks 06d09cde. It is exactly",
"the '<profile>.json' filename the ownerScoped filter just above looks",
"for. Since the CLI's [profile] positional argument overrides this",
"field and always defaults to 'default' when omitted, the Activation",
"command below is not just self-documenting: omitting the positional,",
"or passing anything other than 'linux', makes push look for",
"'default.json' in machine-state/frictions instead of 'linux.json'; it",
"won't find it, and — since this machine's real files ARE present",
"alongside it — push now logs a visible warning and publishes no",
"machine-state/frictions state for that run, instead of the pre-fix",
"silent no-op.",
"",
"Activation: pass BOTH --config (this file) and the profile name",
"positionally:",
"positionally, exactly as shown — it must match 'linux' below:",
" agent-memory-sync run linux --config profiles/linux.json"
],
"profile": "linux",
Expand All @@ -45,7 +69,7 @@
"reachabilityTimeoutMs": 10000,
"syncPaths": [
{ "source": ".", "destination": "memory", "kind": "directory" },
{ "source": "/home/lan/.harness/machine-state", "destination": "machine-state", "kind": "directory" },
{ "source": "/home/lan/.harness/frictions", "destination": "frictions", "kind": "directory" }
{ "source": "/home/lan/.harness/machine-state", "destination": "machine-state", "kind": "directory", "ownerScoped": true },
{ "source": "/home/lan/.harness/frictions", "destination": "frictions", "kind": "directory", "ownerScoped": true }
]
}
59 changes: 43 additions & 16 deletions packages/agent-memory-sync/profiles/mac-mini.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,25 @@
"top-level trees in the same bare repo and never see each other's",
"pushes (a live cross-machine E2E test caught this: mini `pull` after a",
"MacBook `push` reported applied=0). Both profiles now use the same",
"'pandora' value. The 'profile' field below is a DIFFERENT, unrelated",
"setting from repositorySubdir. Precisely: resolveRunConfig() only",
"derives stateDir from 'profile' (.agent-memory-sync/<profile>) when",
"stateDir is NOT set explicitly — this file DOES set stateDir",
"explicitly (below), so 'profile' currently has NO effect on any file",
"path at all; it only ends up recorded as a cosmetic label inside this",
"machine's state.json. Either way it never touches the remote, so it's",
"safe for it to differ (or coincide) across machines — it does differ",
"here: 'mac-mini' vs. macbook.json's 'macbook'.",
"'pandora' value. The 'profile' field below is a DIFFERENT setting from",
"repositorySubdir, and is NOT a cosmetic label (corrected post Fix-Runde",
"agent-tasks 06d09cde — an earlier revision of this comment claimed it",
"was). resolveRunConfig() only derives stateDir from 'profile'",
"(.agent-memory-sync/<profile>) when stateDir is NOT set explicitly —",
"this file DOES set stateDir explicitly (below), so 'profile' still has",
"no effect on stateDir specifically. But collectLocalSyncFiles' push-side",
"ownerScoped filter (src/memory-sync/config.ts) derives the owner",
"filename for the machine-state/frictions entries below from THIS field:",
"push only ever offers '<profile>.json', so it MUST equal this machine's",
"own filename in those directories (mac-mini.json here) or push silently",
"— now: with a visible warning instead — finds no matching owner file",
"and publishes no machine-state/frictions state for this machine. See",
"the Activation note near the end of this comment: the CLI's [profile]",
"positional argument overrides this field and is the more common way to",
"get this mismatch. It still never touches the remote path/tree",
"(repositorySubdir alone determines that), so the raw value is still",
"safe to differ across machines, as it does here: 'mac-mini' vs.",
"macbook.json's 'macbook' — each machine's own real filename.",
"",
"syncPaths' FIRST entry is a directory entry covering the ENTIRE rootDir",
"(source '.'), so every file agent-memory-sync finds there is synced —",
Expand All @@ -53,6 +63,16 @@
"machine's toolchain snapshot for cross-machine parity checks — see",
"docs/machine-setup.md section e) for the payload convention.",
"",
"The machine-state and frictions entries both set \"ownerScoped\": true —",
"each is a one-file-per-machine convention (this machine only ever writes",
"its own <profile>.json, e.g. machine-state/mac-mini.json), so push must",
"only ever offer that one file, never a peer's file this machine merely",
"pulled. See collectLocalSyncFiles' ownerFilter option in",
"src/memory-sync/config.ts and",
".ai/runs/2026-08-03-sync-conflict-markers-echo/03-decisions.md (D-002 to",
"D-004) for the echo/last-writer-wins race this closes. Pull is",
"unaffected — a peer's file is still materialized locally as before.",
"",
"The directory walk skips hidden files/dot-directories (.DS_Store, ._*",
"AppleDouble shadows, .git, ...) by design — see isHiddenEntryName in",
"src/memory-sync/config.ts and src/memory-sync/git-client.ts — so macOS",
Expand Down Expand Up @@ -96,11 +116,18 @@
"Activation: pass BOTH --config (this file) and the 'mac-mini' profile",
"argument, e.g. `agent-memory-sync run mac-mini --config profiles/mac-mini.json`.",
"The CLI's [profile] positional argument always defaults to 'default' and",
"overrides this file's 'profile' field when omitted. Since stateDir is",
"set explicitly below, that override has NO effect on where state files",
"(queue/, base/, tmp/) land here specifically — pass the positional",
"argument anyway for a self-documenting invocation and a correct",
"'profile' label in this run's output/state.json. See docs/machine-setup.md.",
"overrides this file's 'profile' field when omitted — this is now",
"load-bearing, not just a labeling nicety (see the ownerScoped paragraph",
"above): the resolved profile also selects which '<profile>.json' push",
"offers from the machine-state/frictions entries. Omitting the positional",
"(or passing anything other than 'mac-mini') makes push look for",
"'default.json' there instead of 'mac-mini.json'; it won't find it, and —",
"since this machine's real machine-state/frictions files ARE present",
"alongside it — push now logs a visible warning and publishes no",
"machine-state/frictions state for that run, instead of the pre-fix",
"silent no-op. It still has no effect on where state files (queue/,",
"base/, tmp/) land here, since stateDir is set explicitly below. Always",
"pass the positional argument exactly as shown. See docs/machine-setup.md.",
"",
"Reminder (see docs/machine-setup.md / README.md #systemd-unit): `watch`",
"alone is not sufficient even on the mini if you also run it here for",
Expand All @@ -117,7 +144,7 @@
"reachabilityTimeoutMs": 5000,
"syncPaths": [
{ "source": ".", "destination": "memory", "kind": "directory" },
{ "source": "/Users/lannguyensi/.harness/machine-state", "destination": "machine-state", "kind": "directory" },
{ "source": "/Users/lannguyensi/.harness/frictions", "destination": "frictions", "kind": "directory" }
{ "source": "/Users/lannguyensi/.harness/machine-state", "destination": "machine-state", "kind": "directory", "ownerScoped": true },
{ "source": "/Users/lannguyensi/.harness/frictions", "destination": "frictions", "kind": "directory", "ownerScoped": true }
]
}
61 changes: 45 additions & 16 deletions packages/agent-memory-sync/profiles/macbook.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,25 @@
"top-level trees in the same bare repo and never see each other's",
"pushes (a live cross-machine E2E test caught this: mini `pull` after a",
"MacBook `push` reported applied=0). Both profiles now use the same",
"'pandora' value. The 'profile' field below is a DIFFERENT, unrelated",
"setting from repositorySubdir. Precisely: resolveRunConfig() only",
"derives stateDir from 'profile' (.agent-memory-sync/<profile>) when",
"stateDir is NOT set explicitly \u2014 this file DOES set stateDir",
"explicitly (below), so 'profile' currently has NO effect on any file",
"path at all; it only ends up recorded as a cosmetic label inside this",
"machine's state.json. Either way it never touches the remote, so it's",
"safe for it to differ (or coincide) across machines \u2014 it does differ",
"here: 'macbook' vs. mac-mini.json's 'mac-mini'.",
"'pandora' value. The 'profile' field below is a DIFFERENT setting from",
"repositorySubdir, and is NOT a cosmetic label (corrected post Fix-Runde",
"agent-tasks 06d09cde \u2014 an earlier revision of this comment claimed it",
"was). resolveRunConfig() only derives stateDir from 'profile'",
"(.agent-memory-sync/<profile>) when stateDir is NOT set explicitly \u2014",
"this file DOES set stateDir explicitly (below), so 'profile' still has",
"no effect on stateDir specifically. But collectLocalSyncFiles' push-side",
"ownerScoped filter (src/memory-sync/config.ts) derives the owner",
"filename for the machine-state/frictions entries below from THIS field:",
"push only ever offers '<profile>.json', so it MUST equal this machine's",
"own filename in those directories (macbook.json here) or push silently",
"\u2014 now: with a visible warning instead \u2014 finds no matching owner file",
"and publishes no machine-state/frictions state for this machine. See",
"the Activation note near the end of this comment: the CLI's [profile]",
"positional argument overrides this field and is the more common way to",
"get this mismatch. It still never touches the remote path/tree",
"(repositorySubdir alone determines that), so the raw value is still",
"safe to differ across machines, as it does here: 'macbook' vs.",
"mac-mini.json's 'mac-mini' \u2014 each machine's own real filename.",
"",
"syncPaths' FIRST entry is a directory entry covering the ENTIRE rootDir",
"(source '.'), so every file agent-memory-sync finds there is synced \u2014",
Expand All @@ -45,6 +55,16 @@
"machine's toolchain snapshot for cross-machine parity checks \u2014 see",
"docs/machine-setup.md section e) for the payload convention.",
"",
"The machine-state and frictions entries both set \"ownerScoped\": true \u2014",
"each is a one-file-per-machine convention (this machine only ever writes",
"its own <profile>.json, e.g. machine-state/macbook.json), so push must",
"only ever offer that one file, never a peer's file this machine merely",
"pulled. See collectLocalSyncFiles' ownerFilter option in",
"src/memory-sync/config.ts and",
".ai/runs/2026-08-03-sync-conflict-markers-echo/03-decisions.md (D-002 to",
"D-004) for the echo/last-writer-wins race this closes. Pull is",
"unaffected \u2014 a peer's file is still materialized locally as before.",
"",
"The directory walk skips hidden files/dot-directories (.DS_Store, ._*",
"AppleDouble shadows, .git, ...) by design \u2014 see isHiddenEntryName in",
"src/memory-sync/config.ts and src/memory-sync/git-client.ts \u2014 so macOS",
Expand Down Expand Up @@ -86,11 +106,18 @@
"Activation: pass BOTH --config (this file) and the 'macbook' profile",
"argument, e.g. `agent-memory-sync run macbook --config profiles/macbook.json`.",
"The CLI's [profile] positional argument always defaults to 'default' and",
"overrides this file's 'profile' field when omitted. Since stateDir is",
"set explicitly below, that override has NO effect on where state files",
"(queue/, base/, tmp/) land here specifically — pass the positional",
"argument anyway for a self-documenting invocation and a correct",
"'profile' label in this run's output/state.json. See docs/machine-setup.md."
"overrides this file's 'profile' field when omitted — this is now",
"load-bearing, not just a labeling nicety (see the ownerScoped paragraph",
"above): the resolved profile also selects which '<profile>.json' push",
"offers from the machine-state/frictions entries. Omitting the positional",
"(or passing anything other than 'macbook') makes push look for",
"'default.json' there instead of 'macbook.json'; it won't find it, and —",
"since this machine's real machine-state/frictions files ARE present",
"alongside it — push now logs a visible warning and publishes no",
"machine-state/frictions state for that run, instead of the pre-fix",
"silent no-op. It still has no effect on where state files (queue/,",
"base/, tmp/) land here, since stateDir is set explicitly below. Always",
"pass the positional argument exactly as shown. See docs/machine-setup.md."
],
"profile": "macbook",
"rootDir": "/Users/lan/.claude/projects/-Users-lan-git-pandora/memory",
Expand All @@ -109,12 +136,14 @@
{
"source": "/Users/lan/.harness/machine-state",
"destination": "machine-state",
"kind": "directory"
"kind": "directory",
"ownerScoped": true
},
{
"source": "/Users/lan/.harness/frictions",
"destination": "frictions",
"kind": "directory"
"kind": "directory",
"ownerScoped": true
}
]
}
7 changes: 6 additions & 1 deletion packages/agent-memory-sync/src/config/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ interface SyncPathConfig {
destination?: string;
kind?: "file" | "directory";
required?: boolean;
// See src/memory-sync/config.ts's SyncPathConfig.ownerScoped for the full
// rationale — this is the same field, mirrored here since this file
// declares its own structural copy of the shape rather than importing it.
ownerScoped?: boolean;
}

interface UserConfig {
Expand Down Expand Up @@ -435,7 +439,8 @@ function normalizeSyncPathConfigList(value?: SyncPathConfig[]): SyncPathConfig[]
source: entry.source,
destination: entry.destination || entry.source,
kind: entry.kind,
required: Boolean(entry.required)
required: Boolean(entry.required),
ownerScoped: Boolean(entry.ownerScoped)
};
});
}
Expand Down
Loading
Loading