sidecar is the standalone home for an IPC-based sidecars project manager. It owns four product-neutral abstractions:
- Manifest-closed lifecycle —
sidecar.tomldefines command/cwd/args/env/stamps/readiness/inspect/status/stop/reset for every target. - Stamp args — a packed
--sidecar-stamp=v=1;a=<app>;n=<namespace>;m=<mode>;s=<source>;e=<endpoint>flag appended to every spawned target; it is the only sidecar launch metadata contract. - Broker runtime — one project/namespace-scoped loopback TCP broker discovered from
--sidecar-brokerargv identity plus live listener probing; targets receive the broker endpoint through the stampefield. - Inspect bridge — a single-shot SidecarRuntime event frame over a Unix socket (TCP fallback) for talking to a running sidecar's inspect server.
This repository is not a stim.io module. stim.io and other consumers install sidecar as a published CLI through the R2-backed manage.sh / manage.ps1 entrypoints.
sidecar is a local process control plane, not a cluster scheduler and not a local Kubernetes layer. It gives independent local workloads shared lifecycle, identity, discovery, inspect, and reset semantics while preserving their host filesystem, PATH, localhost, shell environment, credentials, and directly inspectable process shape.
Its core posture is:
- Form isolation — server, client, daemon, desktop, and dev-server targets are modeled as distinct workloads with their own wrapper, runtime config, status, readiness, logs, stop, and reset boundary.
- No space isolation — targets still run on the same host, user environment, filesystem, local tools, and loopback network. Do not introduce container image, pod, volume, network namespace, sandbox, or cluster assumptions into the product model.
- Unified control plane — sidecar owns namespace, identity stamps, dynamic endpoint injection, broker/runtime discovery, process lifecycle, inspect, reset, diagnostics, and data-home isolation.
- Business unawareness — product services should remain ordinary HTTP/Vite/Electron/Rust/CLI processes that consume argv, env, cwd, files, and endpoints. They should not need to understand the manifest, broker internals, stamp protocol, or sidecar runtime model.
The TCP broker is local service discovery and runtime registry for host processes. It is not a service mesh, cross-node scheduler, or container networking abstraction.
- Keep
crates/coreproduct-neutral. Nostim,tauri, chat, agent, or message-ledger semantics may leak in. - Keep
crates/corefree of CLI output and process side effects. It exposes config (Manifest), diagnostics, plan, socket parser, stamp protocol, process discovery, and inspect client. - Keep
crates/clias the installed binary boundary namedsidecar. - Manifest fields describe local process control-plane behavior only: start shape, cwd, args, env, readiness, identity, discovery, inspect, stop, reset, and data paths. Do not add product semantics or container/cluster scheduling semantics.
--config <path>is the explicit manifest override. Without it, sidecar walks from cwd upward for the nearestsidecar.toml.- Release assets are R2-backed.
SIDECAR_RELEASES_*repo vars/secrets must be present before any release workflow can run. - Consumer validation must use installed release assets, not
cargo install --path, once a release exists.
- The CLI never carries compatibility shims. Renaming or reshaping
Manifest, CLI flags, the inspect protocol, the stamp protocol, or the installer surface is a hard cutover — no aliases, no deprecation warnings, no best-effort parsing of older shapes. - No internal migrations: there is no
state v1 → v2translator, no schema-version field, no auto-rewrite of usersidecar.toml. Older configs that no longer parse must hard-fail with an error pointing the user at the latest README. - The escape hatch on any breakage is fixed and must always work:
sidecar reset(kill stamped processes) →manage.sh|ps1 uninstall→ reinstall the latest release → re-authorsidecar.tomlper the latest README. This single path replaces every other compatibility guarantee. - Versioning is
0.Y.Zindefinitely. AYbump is breaking by default; pre-1.0 SemVer carries the unstable contract for us — we do not promote to1.0.0. - The update mechanism itself follows the same rule: the startup check is best-effort and silently swallows every failure mode (network, parse, clock, missing curl);
sidecar updateis a thin wrapper around the manager (manage.sh|ps1 update) — it does not decompress, verify, or roll back.
crates/cli reads three optional build-time env vars via option_env! and bakes them into the binary; .forgejo/scripts/release/assets/package.{sh,ps1} set all three from the release workflow:
SIDECAR_BUILD_VERSION→cli::version()(defaults tov<CARGO_PKG_VERSION>for dev builds).SIDECAR_BUILD_CHANNEL→cli::channel()(stable/beta/dev; defaults todev, which disables the startup check andupdatesubcommand).SIDECAR_BUILD_PUBLIC_URL→ fallback for the update check / subcommand when the runtime env var is absent.
The release workflows pass RELEASE_CHANNEL (stable for release.yml, beta for release-beta.yml) and the repo var SIDECAR_RELEASES_PUBLIC_URL into the build matrix steps so that every published binary is self-aware.
SIDECAR_RELEASES_PUBLIC_URL— overrides the build-time stamp for both check and update.SIDECAR_CHANNEL— overrides the build-time channel (e.g. flip a stable build to watch beta).SIDECAR_NO_UPDATE_CHECK=1— skip the startup check entirely.SIDECAR_UPDATE_TTL=<n>[smhd]— startup-check cache TTL; default24h,0= always fetch.
The update cache lives at <data_home>/state/update-<channel>.json (see Data Home below). It is single-key ({checked_at, channel, latest_version}) and may be deleted at any time.
Sidecar's persistent runtime state has a single canonical root, the data home:
- Default:
$XDG_DATA_HOME/sidecar→$HOME/.local/share/sidecaron Unix,%LOCALAPPDATA%\sidecaron Windows. - Layout:
<data_home>/state/— global, namespace-independent (currently: update cache).<data_home>/projects/<namespace>/— per-project isolation (target pids, logs, runtime artifacts).
Override precedence (highest wins): --data-home <path> (CLI) > SIDECAR_DATA_HOME (env) > platform default. The manifest [project].data_dir field replaces the per-project subdir only (it does not move state/); state/ always sits directly under <data_home>.
The CLI accepts -p <name> / --project <name> (and SIDECAR_PROJECT env) as a Docker-Compose-style override of the manifest [project].namespace. It re-keys everything that's namespace-scoped in one shot:
- The stamp protocol's packed
nnamespace field on every spawned sidecar. - The broker protocol's packed
nnamespace field on the project runtime broker. discover_by_namespace/discover_by_app_namespacelookups.- The
<data_home>/projects/<namespace>/subdir.
Precedence: CLI flag > env > manifest. The manifest value becomes a default; CLI always wins. This is what makes the same manifest run as multiple isolated projects on one machine.
sidecar reset --config <path> is the single escape hatch from any incompatible-change failure mode. It is signal-first by default: sidecar sends termination signals to sidecar-owned pids, observes whether they exit, and fails before deleting runtime data if they remain alive. --force is the explicit operator shortcut that escalates to force-kill after the graceful wait.
It:
- Terminates every stamped process and every manifest-recorded target pid in the current namespace.
- Terminates every broker process for the current project/namespace.
- Removes
<data_home>/projects/<namespace>/(manifestdata_dirhonored). - With
--all: also removes<data_home>/state/(wipes update cache, etc.).
There is no --keep-data or confirm prompt by design — predictability and idempotency are reset's contract. Forceful cleanup is still opt-in through --force; it is an operator convenience, not the native lifecycle contract. The install root and bin link are out of scope for reset (they belong to manage.sh|ps1 uninstall). The fully-recovered state is: sidecar reset --all --force when graceful termination is insufficient → manage.sh|ps1 uninstall → reinstall latest → re-author sidecar.toml per the latest README.
Root manage.{sh,ps1} accept exactly: install, update, uninstall. There is no upgrade alias. They default to https://releases.sidecar.perish.uk as the public release asset root, and SIDECAR_RELEASES_PUBLIC_URL / --public-url override it. The CLI's sidecar update subcommand downloads the canonical manager for the current channel and execs it with the update verb.
runseal.toml and .runseal/wrappers/* are thin repo-local operator
entrypoints for support tasks that do not belong in the installable sidecar
product binary. Shared operator logic belongs in Sealkit; .runseal contains
no dedicated TypeScript tests. Local development requires runseal, deno,
Ectropy, and Plumb. Current support commands:
runseal :init— idempotent post-clone validator for tools, repository entrypoints, and versioned Git hooks.runseal :guard— the full local gate: fmt, clippy, tests, Deno checks,plumb doctor ., andectropy --strict ..runseal :land— lands the current clean topic branch through Forgejo, waits for checks on the exact pushed head SHA, squash-merges that SHA, syncsmain, and deletes the branch.--dry-runprints the plan without mutation.runseal :release— dispatches the stable or beta Forgejo release workflow.
Ectropy owns pure AST syntax execution. Plumb owns repository shape, the
canonical ectropy.toml policy, and which paths receive syntax grants. Both
must pass before anything lands:
ectropy.toml— scan roots (crates/**/*.rs,docs/**/*.md), module roots, limits, the comment ban, the single-word rule, and explicit test/environment grants.plumb doctor .— repository layout, operator, workflow, and policy enforcement.ectropy --strict .— syntax execution against that policy.
- Format:
cargo fmt --all --check - Test:
cargo test --locked --workspace - Clippy:
cargo clippy --locked --workspace --all-targets -- -D warnings - CLI smoke:
cargo run --locked -p cli -- doctor --config examples/minimal.toml - Plan:
cargo run --locked -p cli -- plan --config examples/minimal.toml --format json - Repository check:
plumb doctor . && ectropy --strict . - Full gate:
runseal :guard
crates/core/:Manifestconfig, diagnostics, plan, socket parser, stamp protocol, process discovery, inspect client.crates/cli/: CLI parsing, lifecycle execution (start/stop/restart/status/list/reset),inspect <sidecar> <event> [payload], output formatting, exit behavior.manage.shandmanage.ps1: public install/update/uninstall manager entrypoints uploaded as release assets.docs/: durable design notes for planned architecture changes, including the TCP broker runtime direction..runseal/: thin runseal wrapper entrypoints for guard, init, land, and release.ectropy.toml: the Plumb-managed syntax policy Ectropy executes overcrates/anddocs/..forgejo/scripts/: workflow-only release helpers.
After cloning or when the toolchain looks stale, run:
runseal :initIt validates the required tools and repository entrypoints, then installs the
versioned Git hooks. The gates are runseal :guard before landing and the
guard workflow in CI.
Use <area>/<kebab-case-slug>, where <area> matches the touched crate or concern. Examples:
cli/update-commandcore/process-discoveryrelease/stable-dispatchdocs/install-readme
Subject: <area>: <imperative summary> on one line, ideally <= 72 characters. The body explains why the change is shaped this way first, then the concrete change list. End with any Co-Authored-By: trailers when pair-coded or agent-assisted.
Every PR must pass the guard before review:
runseal :guardIt runs, in order:
cargo fmt --all --check
cargo clippy --locked --workspace --all-targets -- -D warnings
cargo test --locked --workspace
deno fmt --check .runseal
deno check --config .runseal/deno.json --lock .runseal/deno.lock --frozen=true .runseal/wrappers/*.ts
plumb doctor .
ectropy --strict .CI reruns the same wrapper: .forgejo/workflows/guard.yml installs Ectropy and
Plumb through the shared actions, then executes .runseal/wrappers/guard.ts on
every PR and every push to main.
Use these top-level sections, in order:
## Why
<what is broken or missing today>
## What
<concrete change list; reference filenames and modules>
## Tests
<commands run and results>Add ## Compatibility when a manifest field, CLI flag, protocol field, output shape, or exit-code behavior moves. Add ## Trade-off worth flagging when the change has a downside that reviewers should hold in mind.
main is PR-only and protected by the repository ruleset main guard. The
required merge gate is the guard check from .forgejo/workflows/guard.yml.
Required approvals are intentionally 0.
From a clean topic branch, default to landing with:
runseal :landIt pushes the branch, creates or reuses the PR, records the exact pushed head
SHA, polls the Forgejo checks on that SHA until every one succeeds,
squash-merges only the audited commit, syncs main, and deletes the branch.
Canonical flag name (consumers must accept and ignore it on their sidecar binaries):
--sidecar-stamp=v=1;a=<sidecar.name>;n=<project.namespace>;m=<sidecar.mode>;s=tool%3Asidecar;e=<runtime-endpoint>
The short keys are v (stamp protocol version), a (app/workload), n (namespace), m (mode), s (source), and e (sidecar runtime endpoint locator). Values are percent-encoded; for example tool:sidecar is encoded as tool%3Asidecar. Discovery uses only this flag via ps -axo pid=,command= on Unix and the Windows PowerShell Win32_Process query on Windows; the implementation is in crates/core/src/runtime/process.rs.
The stamp is the single source of truth for sidecar launch metadata. Do not add env fallbacks or sibling sidecar argv flags for control-plane metadata. Future sidecar launch fields must be encoded inside this stamp contract.
Wire format (one line per direction):
request: {"kind":"event","id":"...","verb":"...","payload":<json>}\n
response: {"kind":"event_response","id":"...","payload":<json>}\n
or {"kind":"event_error","id":"...","error":{"code":"...","message":"..."}}\n
When CLI inspect is called without an explicit payload, the request payload is {} rather than null; typed project protocols should treat this as the unit/no-input event shape.
Default transport is Unix (unix:///absolute/path.sock). TCP is reserved for non-Unix fallback only.
The implementation is crates/core/src/inspect.rs. The CLI orchestration is commands::inspect in crates/cli/src/commands.rs.