Skip to main content
The SDK does not embed ADE as a library. It spawns ade runtime run --socket <path> --profile embedded as a child.
--profile embedded is the only profile ade runtime run accepts. Anything else is a usage error, not a silent fall back to a full brain.

Guest rules

  • Isolated home. Never ~/.ade.
  • Sync is forced off. The sidecar is not a machine peer.
  • No machine authority. machine.updateAndRestart and power transitions are withheld, not merely role-gated.
  • Parent-death watchdog. POSIX does not kill orphans on parent death. The runtime polls ADE_EMBEDDED_PARENT_PID and shuts itself down if the host is gone.
  • Host ADE_* env is scrubbed before spawn so a developer ADE install cannot leak into the guest.

Pidfile reclaim

<home>/runtime.pid records the child. If a previous host died without unwinding, the next createAdeChat reclaims:
  • A healthy runtime already bound to this home is reused.
  • A confirmed-stale child is ended, then a new one is spawned.
  • A pid that cannot be corroborated (recycling, start-time mismatch, pid 0) is left alone and start fails. Spawning a second writer on the same SQLite root is worse.

Events

The client prefers push (personalChats.subscribeEvents). If the runtime omits that capability, it drains personalChats.streamEvents by cursor. doctor().events.mode is "push", "drain", or "unavailable".

When the runtime exits

The runtime is a child process. It can crash, the OS can end it, or its socket can close. The client reports each case with a client event:
Each on returns an unsubscribe function. The listeners stay attached across an autoRestart. Over the Electron bridge a renderer gets the same events. See Electron. Every live thread gets a synthetic status. A runtime that stops mid-turn sends no done and no error. So the SDK sends this envelope on each live thread’s status channel:
Show the thread as stopped when you get it. Approvals that were pending died with the provider process. Their approvalTimeoutMs clocks stop.

autoRestart

autoRestart is off by default. When it is on and the runtime is lost, the client:
  1. Waits backoffMs × 2^(attempt − 1), then starts a runtime and does the handshake again. The first wait is backoffMs.
  2. Connects the event stream again.
  3. Rebinds every thread that it had open. A session that the new runtime still has keeps its thread object, and gets fresh MCP header values from mcpHeaders when its record needs them. A session that the runtime lost is recreated from the stored record, and the thread object that you hold takes the new session. Its thread.id then changes.
  4. Sends restart with ok: true, then transport with state: "reconnected".
After maxAttempts failures the client stops and logs a line. Each failed attempt sends restart with ok: false. The client object does not change, so the threads that you hold and a bridge built on the client keep working. Limits:
  • A turn that was in flight is not resumed. The transcript keeps everything that the runtime persisted.
  • No restart after dispose().
  • requireCompatibleRuntime: true also fails an attempt when the new runtime is outside the supported range.

Version compatibility

The SDK and the runtime release on separate cadences, and a downloaded runtime can be newer or older than the SDK. SUPPORTED_RUNTIME_RANGE is the range that this SDK build is written for. The lower bound has not moved across 0.5: nothing in that line needs a new wire, so a host on runtime 1.2.82 keeps working. @ade-dev/sdk 0.5.1, @ade-dev/chat-ui 0.5.1 and runtime 1.2.83 are the current versions. Patch 0.5.1 carries one tidy that landed on main after 0.5.0 published — the buffered-event category list no longer names cto_voice, a category an agent-role sidecar never receives — so main and the published packages agree again. An embedded runtime used to print four lines on every start — the role-ceiling sentence, its brain.role_ceiling_below_cto warning, and the two “could not bound runtime log” lines for launchd files it never has. All four are gone from runtime 1.2.83; an older runtime still prints them.
After the handshake, the SDK compares the runtime’s reported version with the range:
  • Inside the range: nothing happens.
  • Outside the range: the runtime still connects. The SDK logs one line, and features that the runtime lacks degrade feature by feature. For example, thread.updateMcpServers() throws invalid_option, hydrate: false is ignored, thread.retry() throws unsupported, and attachmentRoots is ignored.
  • With requireCompatibleRuntime: true: createAdeChat throws AdeError("runtime_incompatible") and stops the runtime.
A pre-release suffix is ignored, so 1.2.81-alpha.3 counts as 1.2.81. A development build (0.0.0, or no version) counts as supported, and note says so. A version that the SDK cannot parse counts as not supported. doctor().runtime.compatibility reports the result every time:

doctor()

Use this as the first check when an embedder chat misbehaves. sdkVersion is this package; socket.runtimeVersion is the sidecar binary. They release on separate cadences. See Reference for the full shape.

doctor().runtime

Which of the resolution paths actually produced the running binary, and what the OS thinks of it. source and signature are the two fields to put in a support bundle: together they separate “this app is running the runtime we shipped” from “this app quietly downloaded one on this machine”. The signature probe runs at most once per client, and never throws: codesign -dv --verbose=2 plus spctl --assess --type execute on macOS, Get-AuthenticodeSignature through the trusted System32 PowerShell on Windows, each with a 10 second budget. On macOS, accepted is Gatekeeper’s verdict on the outermost .app that contains the runtime. Gatekeeper judges a nested binary as part of the app that carries it. Judged alone, a correctly signed and notarized runtime inside an app gets “the code is valid but does not seem to be an app”. A runtime outside any .app that gets that answer reports accepted: null. accepted absent or null means “not known”. Only accepted: false means the OS refused it. On Windows, accepted is whether the Authenticode chain is valid. A packaged build that reports source: "downloaded" is a packaging bug. Set allowDownload: false and it becomes a runtime_unavailable throw at startup instead. See Bundling.

Windows

Parity is required. Named pipes, GLOBALROOT System32 tools, ComSpec for .cmd / .bat, and file-lock retries are all in the client. See Install.