Context
resources/content/sandman_handoff.md is gitignored (single line in .gitignore) and only materializes inside the canonical clone where runSandman.mjs is invoked. Once Sandman is functional again on a per-cycle cadence, the handoff file produces strategic priming content (Mathematical Golden Path + Sandman topological alerts) that all three trio members are mandated to consume at boot per AGENTS_STARTUP.md §6 step 4.
But Antigravity-Gemini and Codex-GPT operate against independent clones of neomjs/neo (per memory anchor harness isolation models + the bootstrapWorktree.mjs --canonical-root extension from #10435 for independent-clone topology). Their clones don't have the file. Without symlink mediation similar to the existing --link-data pattern, the strategic priming substrate is invisible to 2 of 3 trio members.
@tobiu surfaced this gap explicitly in the post-merge handoff for the #10311 heartbeat lane: "the sandman handoff only lives inside your repo clone. gitignored. once sandman is functional again, we need to create symlinks inside the other 2 repo folder. similar to what we already do with .neo-ai-data."
The Problem
bootstrapWorktree.mjs --link-data currently symlinks 6 gitignored subdirs of .neo-ai-data/ (sqlite, chroma, wake-daemon, backups, datasets, neo-sqlite) from canonical → other clones, preserving the granular gitignore boundary that protects the tracked concepts/ subdir per #10432. That mechanism doesn't extend to single gitignored files outside .neo-ai-data/.
Two cross-clone substrates that should be unified by analogous symlinks:
resources/content/sandman_handoff.md (this ticket) — Sandman-produced strategic priming consumed at boot.
- (Future, not in this ticket) Any other gitignored cross-clone artifacts that surface as the swarm primitives expand. The implementation should be allowlist-shaped to keep the substrate extensible without per-artifact code changes.
Without this:
- @neo-gemini-3-1-pro's Antigravity clone reads an empty / nonexistent file at boot —
view_file resources/content/sandman_handoff.md skipped. Strategic context divergence.
- @neo-gpt's Codex clone same.
- Cross-clone visibility is asymmetric: only the operator running
runSandman.mjs from canonical sees the handoff.
The Architectural Reality
The symlink discipline lives in ai/scripts/bootstrapWorktree.mjs. The existing primitives:
BOOTSTRAP_CONFIGS constant — list of files to copy (gitignored config.mjs files; cannot symlink because Node ESM resolver walks to canonical path → namespace collision in setupClass).
DATA_SUBDIRS_TO_LINK constant — list of .neo-ai-data/ subdirs to symlink (pure data; no ESM imports).
symlinkDataDir() function — granular per-subdir symlink with already-linked / no-source / clobber-with-force semantics; never touches concepts/ per #10432 protection.
The handoff file fits the symlink category (pure data, no ESM imports). It does NOT fit the existing symlinkDataDir() because the file lives outside .neo-ai-data/ (in resources/content/) and the function targets a single specific parent dir.
The cleanest extension: a parallel primitive for gitignored single files that the symlink mechanism is permitted to handle. Naming and scope bounded by the same allowlist discipline.
The Fix
Extend ai/scripts/bootstrapWorktree.mjs:
Add GITIGNORED_FILES_TO_LINK constant — initial list ['resources/content/sandman_handoff.md']. JSDoc explains the cross-clone-handoff rationale + the allowlist-shape extensibility hook.
Add symlinkGitignoredFiles() function — granular per-file symlink with:
'already-linked' (lstat reports symlink) → skip.
'skipped-no-source' (canonical file doesn't exist, e.g. before first Sandman run) → skip gracefully. This is the fresh-state path; agents should not be blocked by absent canonical handoffs.
'skipped-real-file' (dst is a regular file) → skip with warning + pointer to manual rm for the user. No --force semantic for files since clobbering a single artifact is more nuanced than data dirs (potential prior local state worth preserving).
- Otherwise →
fs.symlink(src, dst, 'file').
- Idempotent. Returns
{linked, alreadyLinked, skippedNoSource, skippedRealFile, mainCheckout} per-file action map (parallel to symlinkDataDir).
Wire into CLI under --link-data flag — after symlinkDataDir() runs, invoke symlinkGitignoredFiles() on the same mainCheckout + projectRoot. Add a per-file summary line to the CLI summary (linked / already-linked / skipped-no-source / skipped-real-file counts).
Update file-head JSDoc — extend the existing "Symlinks: code vs data" section with a "single files" sub-section noting the new allowlist + cross-reference to this ticket. Anchor & Echo on Cross-clone substrate unification.
No new flag — fold under existing --link-data. The user-facing semantics of "link the gitignored substrate" are consistent across data dirs and single files; adding a separate --link-handoff would fragment the bootstrap surface.
Acceptance Criteria
Out of Scope
- Implementing or scheduling Sandman itself (
runSandman.mjs already exists; this ticket only enables cross-clone visibility once it produces the handoff). #10311's heartbeat lane is the parallel substrate for invocation-cadence work.
- Re-enabling
autoDream / autoGoldenPath defaults — strictly forbidden per #10569 hard-stop until #10186 / #10103 / #10063 land.
- Symlinking other paths in
resources/content/ — this directory is heavily git-tracked; only sandman_handoff.md is gitignored. Future cross-clone artifacts can extend GITIGNORED_FILES_TO_LINK if they share the same gitignore + canonical-only-write semantic.
- Adding a
--link-handoff flag separate from --link-data — explicitly rejected; consolidation under existing flag preserves single-substrate framing.
Avoided Traps
- Trap: symlink the parent
resources/content/ directory — would hide all the tracked issue/PR/discussion files behind canonical's view. Same anti-pattern that #10432 fixed for .neo-ai-data/ parent symlink with the concepts/ subdir clobber. Per-file granularity preserves the gitignore boundary.
- Trap: copy instead of symlink — would leave each clone's handoff stale until next bootstrap run. Symlink ensures live coherence: when canonical's
runSandman.mjs rewrites the handoff, all linked clones see the new content immediately.
- Trap:
force clobber semantics on single files — for data dirs, --force is justified (corruption recovery). For a single artifact file, the user's local state may be intentional (e.g., a saved prior handoff). Conservative skip-with-warning is the safer default. Users can manually rm if they want the symlink.
- Trap: re-propose flipping
autoDream defaults — that path was rejected per #10569 (closed as not planned). This ticket is about cross-clone visibility once Sandman runs, not about scheduling Sandman.
Related
- Parent epic: #10311 (Institutionalizing Swarm Autonomy — Phase 1 REM Sleep & A2A) — heartbeat substrate.
- Adjacent: #10095 bootstrapWorktree origin, #10432 granular per-subdir symlink refinement, #10435 independent-clone topology extension.
- Strategic gates (NOT blocked-by, but inform the broader autonomy substrate): #10186 MCP concurrency, #10103 SDK config migration, #10063 auto-persist turn memories.
Origin Session ID: 86b7a3a0-7b14-4bd1-b707-52c5741aaeeb
Retrieval Hint: "cross-clone sandman_handoff symlink bootstrapWorktree GITIGNORED_FILES_TO_LINK"
Context
resources/content/sandman_handoff.mdis gitignored (single line in.gitignore) and only materializes inside the canonical clone whererunSandman.mjsis invoked. Once Sandman is functional again on a per-cycle cadence, the handoff file produces strategic priming content (Mathematical Golden Path + Sandman topological alerts) that all three trio members are mandated to consume at boot perAGENTS_STARTUP.md §6step 4.But Antigravity-Gemini and Codex-GPT operate against independent clones of
neomjs/neo(per memory anchorharness isolation models+ the bootstrapWorktree.mjs--canonical-rootextension from #10435 for independent-clone topology). Their clones don't have the file. Without symlink mediation similar to the existing--link-datapattern, the strategic priming substrate is invisible to 2 of 3 trio members.@tobiu surfaced this gap explicitly in the post-merge handoff for the #10311 heartbeat lane: "the sandman handoff only lives inside your repo clone. gitignored. once sandman is functional again, we need to create symlinks inside the other 2 repo folder. similar to what we already do with .neo-ai-data."
The Problem
bootstrapWorktree.mjs --link-datacurrently symlinks 6 gitignored subdirs of.neo-ai-data/(sqlite, chroma, wake-daemon, backups, datasets, neo-sqlite) from canonical → other clones, preserving the granular gitignore boundary that protects the trackedconcepts/subdir per #10432. That mechanism doesn't extend to single gitignored files outside.neo-ai-data/.Two cross-clone substrates that should be unified by analogous symlinks:
resources/content/sandman_handoff.md(this ticket) — Sandman-produced strategic priming consumed at boot.Without this:
view_file resources/content/sandman_handoff.mdskipped. Strategic context divergence.runSandman.mjsfrom canonical sees the handoff.The Architectural Reality
The symlink discipline lives in
ai/scripts/bootstrapWorktree.mjs. The existing primitives:BOOTSTRAP_CONFIGSconstant — list of files to copy (gitignored config.mjs files; cannot symlink because Node ESM resolver walks to canonical path → namespace collision insetupClass).DATA_SUBDIRS_TO_LINKconstant — list of.neo-ai-data/subdirs to symlink (pure data; no ESM imports).symlinkDataDir()function — granular per-subdir symlink with already-linked / no-source / clobber-with-force semantics; never touchesconcepts/per #10432 protection.The handoff file fits the symlink category (pure data, no ESM imports). It does NOT fit the existing
symlinkDataDir()because the file lives outside.neo-ai-data/(inresources/content/) and the function targets a single specific parent dir.The cleanest extension: a parallel primitive for gitignored single files that the symlink mechanism is permitted to handle. Naming and scope bounded by the same allowlist discipline.
The Fix
Extend
ai/scripts/bootstrapWorktree.mjs:Add
GITIGNORED_FILES_TO_LINKconstant — initial list['resources/content/sandman_handoff.md']. JSDoc explains the cross-clone-handoff rationale + the allowlist-shape extensibility hook.Add
symlinkGitignoredFiles()function — granular per-file symlink with:'already-linked'(lstat reports symlink) → skip.'skipped-no-source'(canonical file doesn't exist, e.g. before first Sandman run) → skip gracefully. This is the fresh-state path; agents should not be blocked by absent canonical handoffs.'skipped-real-file'(dst is a regular file) → skip with warning + pointer to manualrmfor the user. No--forcesemantic for files since clobbering a single artifact is more nuanced than data dirs (potential prior local state worth preserving).fs.symlink(src, dst, 'file').{linked, alreadyLinked, skippedNoSource, skippedRealFile, mainCheckout}per-file action map (parallel tosymlinkDataDir).Wire into CLI under
--link-dataflag — aftersymlinkDataDir()runs, invokesymlinkGitignoredFiles()on the samemainCheckout+projectRoot. Add a per-file summary line to the CLI summary (linked / already-linked / skipped-no-source / skipped-real-file counts).Update file-head JSDoc — extend the existing "Symlinks: code vs data" section with a "single files" sub-section noting the new allowlist + cross-reference to this ticket. Anchor & Echo on
Cross-clone substrate unification.No new flag — fold under existing
--link-data. The user-facing semantics of "link the gitignored substrate" are consistent across data dirs and single files; adding a separate--link-handoffwould fragment the bootstrap surface.Acceptance Criteria
GITIGNORED_FILES_TO_LINKconstant exported frombootstrapWorktree.mjs. Initial list containsresources/content/sandman_handoff.md.symlinkGitignoredFiles()function exported. Per-file action map matches the spec: linked / already-linked / skipped-no-source / skipped-real-file.mainCheckoutshort-circuit.--link-dataflag invokessymlinkGitignoredFiles()aftersymlinkDataDir(). Summary line appended to existing data-symlink summary.--link-datatwice on the same worktree results in'already-linked'on cycle 2 with no error.sandman_handoff.md(the current state until Sandman runs), CLI reportsskipped-no-source: 1without erroring.GITIGNORED_FILES_TO_LINKallowlist purpose documented; this ticket cross-referenced via@see.test/playwright/unit/ai/scripts/bootstrapWorktree.spec.mjs(if exists; else add) covering the four per-file states + the mainCheckout no-op.Out of Scope
runSandman.mjsalready exists; this ticket only enables cross-clone visibility once it produces the handoff). #10311's heartbeat lane is the parallel substrate for invocation-cadence work.autoDream/autoGoldenPathdefaults — strictly forbidden per #10569 hard-stop until #10186 / #10103 / #10063 land.resources/content/— this directory is heavily git-tracked; onlysandman_handoff.mdis gitignored. Future cross-clone artifacts can extendGITIGNORED_FILES_TO_LINKif they share the same gitignore + canonical-only-write semantic.--link-handoffflag separate from--link-data— explicitly rejected; consolidation under existing flag preserves single-substrate framing.Avoided Traps
resources/content/directory — would hide all the tracked issue/PR/discussion files behind canonical's view. Same anti-pattern that #10432 fixed for.neo-ai-data/parent symlink with theconcepts/subdir clobber. Per-file granularity preserves the gitignore boundary.runSandman.mjsrewrites the handoff, all linked clones see the new content immediately.forceclobber semantics on single files — for data dirs,--forceis justified (corruption recovery). For a single artifact file, the user's local state may be intentional (e.g., a saved prior handoff). Conservative skip-with-warning is the safer default. Users can manuallyrmif they want the symlink.autoDreamdefaults — that path was rejected per #10569 (closed asnot planned). This ticket is about cross-clone visibility once Sandman runs, not about scheduling Sandman.Related
Origin Session ID: 86b7a3a0-7b14-4bd1-b707-52c5741aaeeb Retrieval Hint: "cross-clone sandman_handoff symlink bootstrapWorktree GITIGNORED_FILES_TO_LINK"