stacklok / stacklok/mecatl

SDK: spawn-and-connect (singleton daemon) mode for Node clients

Open
#1,350 1 comment 0 reactions 1 assignee View on GitHub

@kantord is already working on this.

Since Sep 10, 2026.

enhancement
Dominant language
Go
Stars
152
Forks
16
Avg merge
14h 48m
Merged PRs (30d)
536

Description

Parent

Sub-issue of #821 (which is the implementation plan for epic #761).

Problem

spawn() and connect() cover two ends of a spectrum: a private, ephemeral, per-process daemon (spawn(), auto-exits via the lifetime pipe when its owning process dies) versus attaching to an already-known, externally-managed daemon (connect()). Neither covers a common third case for CLI-style Node clients: several independent process launches that want to share one daemon and its live sessions, without the caller having to stand up and manage that daemon as a separate long-running service themselves.

Motivating case

mecavim (a minimal Neovim client for mecated, an internal prototype) currently calls spawn({}) on every launch. Each launch gets a brand-new private daemon with in-memory-only session state. This makes --resume/--resume-latest (session picked via sessions.get(id) / sessions.list()) structurally unable to find anything from a previous launch: the daemon that held that session already exited (by design — that's what the lifetime pipe is for) before the next launch starts. Adding --store-dir would let a session survive on disk and be reconstructed via session.activity(), but the AI's live conversational state — and any true "reconnect to exactly where I left off" experience — is gone the moment that private daemon exits.

The fix needs a daemon that outlives any single launch, addressable by subsequent launches, without the client having to build its own "is one already running, else start one" lifecycle logic (locking, idle shutdown, etc.) — that's the same complexity spawn() already exists to hide for the private-daemon case.

Proposal

A third node-only entry point (name TBD — spawnAndConnect(), ensure(), something in that family) that:

  1. Tries to connect() to a daemon at a well-known, stable address (not spawn()'s private per-call temp-dir socket).
  2. If nothing answers, spawns one at that address instead of a private one, so later callers can find it.
  3. Does not wire up spawn()'s lifetime pipe (the whole point is that this daemon outlives its first caller).
Why this is a real feature, not a thin wrapper
  • Race safety. Two callers starting near-simultaneously and both doing "spawn if nothing's there" need a lock (e.g. atomic bind-or-connect on the socket) or you get two daemons contending for one address.
  • Idle shutdown. For this to be a good citizen (not an orphan process running forever after the last client disconnects), the daemon itself needs to track "no clients connected for N minutes" and self-exit. That's server-side (mecated serve) logic, not something the TS client can enforce from the outside.
  • Version skew. A long-lived daemon started by an older mecated binary reconnected to by a newer SDK client needs the existing ServerCapabilities feature-bit check (already used for spawn()/connect() skew detection) applied the same way here.

Non-goals (for a first version)

  • Multi-machine/network discovery — this is about one user's machine, a well-known local address (e.g. a UDS path under XDG state dir) is enough.
  • Automatic --store-dir provisioning — this is a separate, complementary concern (already flagged as a deliberate non-addition on #821's M3).

Fully or partially written by an AI agent.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.