11 KiB
Workspace App Runtime
Workspace App Center apps and daemon-managed ACP npm adapters run against a daemon-managed runtime baseline. App packages must not bundle or declare Python/Node versions; Tutti injects the managed runtime paths at launch.
Bootstrap Contract
Every Workspace App package exposes one POSIX-compatible bootstrap.sh through
runtime.bootstrap. The app lifecycle depends on this contract rather than on
a platform implementation:
- macOS and Linux execute the script through the host's normal executable path;
- packaged Windows Desktop injects a Tutti-managed Bash runtime and the Windows shell adapter invokes the same script;
- fat packages select their native artifact with the injected
TUTTI_PLATFORMvalue (darwin-arm64,darwin-amd64, orwindows-amd64).
Keep bootstrap scripts LF-terminated and limited to POSIX shell built-ins plus
the commands supplied by the managed Windows shell: cat, cp, dirname,
mkdir, mv, rm, sleep, and uname. Use the explicit managed runtime
variables for Node and Python instead of expecting package managers or other
Unix tools in the shell. Do not add a second manifest entrypoint map or require
Git for Windows. The optional App Factory prepare.sh uses the same shell
adapter and command subset.
App State Directories
Each installed app receives installation-scoped directories below the Tutti
state root. The runner creates them before bootstrap.sh starts and injects:
TUTTI_APP_DATA_DIRfor durable artifacts and non-database state;TUTTI_APP_DATABASE_DIRfor host-local durable active databases, including SQLite WAL/SHM files and indexes;TUTTI_APP_RUNTIME_DIRfor restartable scratch/runtime state;TUTTI_APP_LOG_DIRfor backend logs.
An app may create multiple database files under TUTTI_APP_DATABASE_DIR.
Keeping live databases separate prevents synchronized, referenced, or exported
data paths from weakening filesystem locking and atomic-write guarantees. App
restarts and package upgrades preserve the database directory; uninstalling the
workspace app removes it together with the installation state root.
TUTTI_CLI is the app-facing CLI contract. Apps and skills must not depend on a
host-internal CLI configuration variable: the injected command is responsible
for locating and authenticating its own daemon connection.
Runtime Baseline
The baseline runtime is componentized. The default baseline profile contains the Python and Node components, but each component is published as a separate platform-specific zip so tuttid can download them in parallel:
python component zip:
python/bin/python3
node component zip:
node/bin/node
node/bin/npm
node/bin/npx
node/bin/corepack
rtk component zip (separate `rtk-saver` profile):
rtk/bin/rtk
rtk/LICENSE
Windows runtime artifacts, when added, must use the Windows executable names expected by tuttid:
python/bin/python.exe
node/bin/node.exe
node/bin/npm.cmd
node/bin/corepack.cmd
rtk/bin/rtk.exe
Catalog platform keys must use Go runtime names because tuttid resolves them with runtime.GOOS and runtime.GOARCH. Use darwin-amd64 and linux-amd64, not Node's darwin-x64 or linux-x64 download labels.
Release Ownership
Runtime artifacts are released independently from desktop packages. Do not publish runtime artifacts from .github/workflows/desktop-release.yml.
The runtime release source of truth is:
config/tutti.app-runtime.lock.json
When Python, Node, RTK, uv, supported platforms, or artifact layout changes, update the lock and run the runtime release workflow once. Fixed versions do not require rebuilding on every product release.
The workflow is:
.github/workflows/publish-tutti-app-runtime.yml
The workflow:
- Installs the pinned uv version on macOS and Linux.
- Uses uv to install the pinned macOS/Linux Python baseline.
- Downloads the pinned official CPython embeddable package on Windows, verifies its SHA-256, and requires valid Authenticode signatures on every
.exe,.dll, and.pydfile. - Downloads the pinned Node release for each platform and verifies it against Node's
SHASUMS256.txt. - Downloads the pinned RTK release and Apache-2.0 license, verifies byte size
and SHA-256, and checks
rtk --version. - Assembles separate Python, Node, and RTK zips per platform.
- Writes metadata for each platform's runtime components.
- Uploads immutable component zips to S3.
- Builds and uploads
catalog.json.
Runtime artifacts should be uploaded under a dedicated S3 prefix, normally:
tutti-app-runtimes/<runtimeVersion>/<platform>/python/tutti-app-runtime-python-<platform>-<runtimeVersion>.zip
tutti-app-runtimes/<runtimeVersion>/<platform>/node/tutti-app-runtime-node-<platform>-<runtimeVersion>.zip
tutti-app-runtimes/<runtimeVersion>/<platform>/rtk/tutti-app-runtime-rtk-<platform>-<runtimeVersion>.zip
tutti-app-runtimes/catalog.json
The public artifact base URL must point at the same prefix, usually through CloudFront:
https://<cloudfront-domain>/tutti-app-runtimes
When TUTTI_APP_RUNTIME_CATALOG is unset, tuttid uses the default published runtime catalog:
https://d1x7gb6wqsqmnm.cloudfront.net/tutti-app-runtimes/catalog.json
Artifacts are immutable and should use long cache headers. The catalog is mutable and should use a short cache header.
The production runtime catalog must publish at least the runtimeVersion locked
by the desktop release target for every supported platform before that desktop
release is promoted. Runtime versions use an ordered YYYY.MM.PATCH format and
newer runtime releases remain compatible with older desktop releases because
tuttid always resolves the mutable catalog's current entry. The promotion
workflow enforces this ordering with
tools/scripts/verify-tutti-app-runtime-release.mjs. Merging a change to
config/tutti.app-runtime.lock.json on main automatically runs Publish Tutti App Runtime; the manual trigger remains available for recovery. Desktop
releases check the production catalog before reserving a tag or starting builds,
and promotion repeats the check as a final safety gate.
Catalog Shape
The runtime catalog consumed by tuttid has this shape:
{
"schemaVersion": "tutti.app.runtimes.v2",
"runtimes": {
"darwin-arm64": {
"version": "2026.06.0",
"components": {
"python": {
"version": "3.12.13",
"artifactUrl": "https://cdn.example.test/tutti-app-runtimes/2026.06.0/darwin-arm64/python/tutti-app-runtime-python-darwin-arm64-2026.06.0.zip",
"artifactSha256": "64-char-sha256",
"artifactSizeBytes": 123
},
"node": {
"version": "22.22.3",
"artifactUrl": "https://cdn.example.test/tutti-app-runtimes/2026.06.0/darwin-arm64/node/tutti-app-runtime-node-darwin-arm64-2026.06.0.zip",
"artifactSha256": "64-char-sha256",
"artifactSizeBytes": 456
},
"rtk": {
"version": "0.45.0",
"artifactUrl": "https://cdn.example.test/tutti-app-runtimes/2026.06.0/darwin-arm64/rtk/tutti-app-runtime-rtk-darwin-arm64-2026.06.0.zip",
"artifactSha256": "64-char-sha256",
"artifactSizeBytes": 789
}
},
"profiles": {
"baseline": ["python", "node"],
"connector-node-static": ["node"],
"rtk-saver": ["rtk"]
}
}
}
}
tuttid resolves the baseline profile by default when launching apps, and may
preload smaller profiles such as connector-node-static during daemon startup or an
explicit runtime-preparation workflow before first launch. Listing App Center
apps must not preload runtimes as a side effect. App manifests must not declare
a runtime kind. The Windows managed runtime publishes the same Python+Node
baseline profile as Unix so existing Python apps (for example Automation)
remain portable. Apps that only need Node may declare
runtime.profile: "connector-node-static" so launch does not require the Python
component. If runtime requirements need to become more selective later, add a
capability list such as runtime component requirements rather than restoring a
single-kind manifest field. macOS and Linux currently use Python 3.12.13 from
the pinned uv-managed distribution. Windows uses python.org's official signed
Python 3.12.10 embeddable package because Python 3.12.10 is the last 3.12 patch
release with official Windows binaries; later 3.12 security releases are
source-only. The Windows version, archive URL, and SHA-256 are pinned separately
in config/tutti.app-runtime.lock.json, and platform metadata must report the
effective version rather than the cross-platform default.
The Windows embeddable package is intentionally minimal and isolated. It does
not include pip, venv, or ensurepip. Windows workspace apps using the
baseline profile must ship their dependencies in the app package and must not
install packages into the managed runtime. Moving Windows to a later Python
patch requires either an official signed binary distribution or Authenticode
signing of every shipped .exe, .dll, and .pyd file with an approved Tutti
publisher identity. SHA-256 verification alone is not a code-signing substitute.
Runtime Overrides
Supported daemon overrides:
TUTTI_APP_RUNTIME_CATALOG: HTTP(S) URL or local file path for the runtime catalog. Set it to an empty string to disable the default runtime catalog.TUTTI_APP_RUNTIME_CACHE_ROOT: cache root for platform-specific runtime directories.TUTTI_APP_RUNTIME_ROOT: exact prepared runtime root, mainly for tests and local debugging.TUTTI_BUNDLED_RTK_PATH: exact packaged RTK executable used by Desktop; development and non-Desktop hosts may override it for packaging diagnostics.TUTTI_MANAGED_POSIX_SHELL: absolute path to the managed POSIX shell executable. Packaged Windows Desktop sets this automatically; the Workspace App adapter consumes it, while the override exists for development, tests, and packaging diagnostics.
App packages must not set these variables. The runner injects TUTTI_APP_PYTHON, TUTTI_APP_NODE, TUTTI_APP_NPM, and PATH for app processes. RTK is
not part of the Workspace App baseline; Agent saver mode and the Tutti terminal
resolve the dedicated rtk-saver profile.
Agent provider installers may also use the managed TUTTI_APP_NPM path to
install ACP npm adapters into daemon-owned per-agent prefixes instead of npm
global locations.
Runtime artifacts must make node/bin/npm, node/bin/npx, and
node/bin/corepack standalone wrappers that execute the packaged Node binary
with their packaged CLI scripts. Do not rely on Node release symlinks surviving
zip packaging. The resolver treats a dereferenced Corepack distribution entry
as an invalid Node component and downloads the current catalog artifact again,
so existing caches recover when this wrapper contract changes.
Validation
After runtime release changes, run:
node --test ./tools/scripts/build-tutti-app-runtime-catalog.test.mjs
pnpm lint:ts
After downloader or runner changes, also run:
cd services/tuttid && go test ./service/workspace ./service/eventstream
pnpm --filter @tutti-os/workspace-app-center test
pnpm --filter @tutti-os/desktop typecheck