LearnNewsExamplesServices
Frontmatter
id10591
titleCross-clone symlink for sandman_handoff.md in bootstrapWorktree
stateClosed
labels
enhancementaiarchitecture
assigneesneo-opus-4-7
createdAtMay 1, 2026, 8:02 PM
updatedAtMay 1, 2026, 8:28 PM
githubUrlhttps://github.com/neomjs/neo/issues/10591
authorneo-opus-4-7
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtMay 1, 2026, 8:28 PM

Cross-clone symlink for sandman_handoff.md in bootstrapWorktree

Closedenhancementaiarchitecture
neo-opus-4-7
neo-opus-4-7 commented on May 1, 2026, 8:02 PM

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:

  1. resources/content/sandman_handoff.md (this ticket) — Sandman-produced strategic priming consumed at boot.
  2. (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:

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

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

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

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

  • (AC1) GITIGNORED_FILES_TO_LINK constant exported from bootstrapWorktree.mjs. Initial list contains resources/content/sandman_handoff.md.
  • (AC2) symlinkGitignoredFiles() function exported. Per-file action map matches the spec: linked / already-linked / skipped-no-source / skipped-real-file. mainCheckout short-circuit.
  • (AC3) CLI under --link-data flag invokes symlinkGitignoredFiles() after symlinkDataDir(). Summary line appended to existing data-symlink summary.
  • (AC4) Idempotency: running --link-data twice on the same worktree results in 'already-linked' on cycle 2 with no error.
  • (AC5) Graceful absent-source: when canonical doesn't have sandman_handoff.md (the current state until Sandman runs), CLI reports skipped-no-source: 1 without erroring.
  • (AC6) File-head JSDoc updated: new "single files" sub-section under "Symlinks: code vs data"; GITIGNORED_FILES_TO_LINK allowlist purpose documented; this ticket cross-referenced via @see.
  • (AC7) Test coverage: extend 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

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

tobiu referenced in commit ef68487 - "feat(ai): cross-clone symlink for sandman_handoff.md (#10591) (#10592) on May 1, 2026, 8:28 PM
tobiu closed this issue on May 1, 2026, 8:28 PM