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.updateAndRestartand 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_PIDand 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:
approvalTimeoutMs clocks stop.
autoRestart
autoRestart is off by default. When it is on and the runtime is lost, the client:
- Waits
backoffMs × 2^(attempt − 1), then starts a runtime and does the handshake again. The first wait isbackoffMs. - Connects the event stream again.
- 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
mcpHeaderswhen 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. Itsthread.idthen changes. - Sends
restartwithok: true, thentransportwithstate: "reconnected".
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: truealso 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.
- 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()throwsinvalid_option,hydrate: falseis ignored,thread.retry()throwsunsupported, andattachmentRootsis ignored. - With
requireCompatibleRuntime: true:createAdeChatthrowsAdeError("runtime_incompatible")and stops the runtime.
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()
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.