Tuturuuu SDK
Official TypeScript/JavaScript SDK for interacting with the Tuturuuu platform. Provides type-safe access to workspace storage, files, documents, and agent-friendly task workflows.Installation
Quick Start
Features
Source previews support nativettr backlog search help --lang en|vi --json
and ttr backlog search model-status --json before login. Lexical queries require
an explicit --db.
Model status without --runtime-dir loads or downloads no model. These source
commands do not establish published npm availability or model admission. See
local operational state.
Search indexing reads the full current projection and rebuilds every lexical
document with complete member content and provenance. Under the same source UUID,
projection version, and authenticated runtime fingerprint, it can reuse a vector
for an identical record ID and versioned full-record content hash. Canonical
membership, provenance, content, or runtime recipe changes invalidate affected
vectors. Deleted records are omitted. An absent or revoked runtime removes all
semantic vectors. An older content policy requires one rebuild.
Index output reports strategy: snapshot-reconciled-vector-reuse-v1,
reuseEligible, reused, computed, removed, and invalidated. embedded
counts committed vectors, with a maximum corpus of eight documents. incremental
means reuse eligibility. projectionScan and lexicalRebuild remain full-snapshot
operations; indexing does not consume an event delta feed. A newer cached revision
or equal-revision snapshot drift denies reuse.
modelLoaded reports a successful non-injected embedding return in this command.
A command that computes no new vectors authenticates the runtime without loading
the model. embeddingAttemptsThisCommand and
successfulEmbeddingsThisCommand also describe this command. A later
embedding failure can leave modelLoaded: true while the committed lexical
fallback has zero vectors. Partial semantic caches are never published.
Structural cache, projection, and source conflicts propagate as errors. Runtime
embedding failures and capacity failures use lexical fallback. Before publication,
the index rereads source UUID, revision, snapshot, version, and cache permission.
Queries require the current snapshot and exact document IDs and content keys.
An older content policy produces stale-content-policy and lexical fallback.
Similarity candidates remain uncertain and never authorize closure or merge.
Source tests and review qualify this reuse behavior separately from real engine
execution, installed CLI availability, and hosted SDK packaging.
SDK package qualification drains each owned process group after termination. On Darwin, a group containing only exiting or zombie members can briefly return EPERM to a signal-zero probe. The drain keeps polling within its existing 1500 ms budget and succeeds only after ESRCH; persistent permission errors remain PROCESS_GROUP_UNDRAINED with the last probe error as cause, while other probe errors are rethrown unchanged. The post-leader-close SIGKILL alone may continue to drain after EPERM; other termination failures remain errors. Regression coverage lives in scripts/ci/sdk-package-process.test.js and the unchanged real-descendant assertions in qualify-sdk-package.test.js. Apple’s process-group implementation filters zombies and can return EPERM when no eligible member remains.
Bun CLI
Use
ttr for browser login, workspace discovery, and task workflowsHeadless Login
Copy a short-lived CLI token from the browser when a local callback is not
available
Storage Operations
Upload, download, list, and delete files and folders
Document Management
Create, read, update, and delete workspace documents
Signed URLs
Generate temporary shareable links for files
Image Resizing
Apply Supabase image transformations during share and download
Analytics
Track storage usage, file counts, and limits
Type-check and declaration output
The SDKtype-check command uses tsconfig.typecheck.json, which extends the
public build configuration to retain its source files and semantic checking options.
It disables emit, composite output and incremental state, so it owns no dist
files even when the CI task graph also schedules the SDK build. The portable
build remains responsible for JavaScript and public declarations; its configuration
remains separate from the no-emit check. sdk-typecheck-boundary.test.ts checks resolved compiler
coverage/options and exercises a disposable type error and successful check without
output files. Exact-head CI must verify both type-check and the real portable build.
This structural boundary does not identify the writer behind a historical
ENOTEMPTY failure.
Portable staging excludes test/spec/acceptance modules and the shared
resources-test-fixtures.ts helper. Test-only imports such as Vitest’s fixture
hooks must not enter the public runtime closure. The actual-source control in
packages/sdk/scripts/portable-build.test.cjs verifies this before compilation.
CLI
For offline request ledgers and separate operational notes, see Local operational state. These groups run before authentication and update checks and do not access platform data. The SDK package includes a native Bun-powered CLI.ttr is the primary command,
and tuturuuu plus tutur3u are aliases for the same binary.
ttr login opens the browser and creates a Tuturuuu-managed CLI app-session
JWT pair through apps/web. The terminal and browser confirmation page show the
signed-in account email when the web session exposes it. The CLI stores that
session and selected workspace, board, list, task, label, and project in the OS
app config directory. personal is the default workspace after login and when
no workspace has been selected:
Use ttr host use to switch between Tuturuuu origins. production and prod
resolve to https://tuturuuu.com; local and localhost prefer safe local
environment URLs from TUTURUUU_LOCAL_BASE_URL, PORTLESS_URL, WEB_APP_URL,
NEXT_PUBLIC_WEB_APP_URL, or NEXT_PUBLIC_APP_URL, then fall back to
https://tuturuuu.localhost. Pass --port <port> for
http://localhost:<port>, or --portless --port <port> for
https://tuturuuu.localhost:<port>. Changing origins clears the saved session
and selected workspace context because both are origin-specific.
ttr config set-base-url <url> remains available for compatibility and uses the
same host normalization and context-clearing behavior. The CLI normalizes
0.0.0.0 and unspecified IPv6 origins to localhost so the browser login flow
does not open a blocked address.
On Dockerized production deployments, the web auth start route also rewrites
wildcard bind origins such as 0.0.0.0:7803 to the forwarded public host or the
configured app URL (WEB_APP_URL, NEXT_PUBLIC_WEB_APP_URL,
NEXT_PUBLIC_APP_URL, COOLIFY_URL, or COOLIFY_FQDN) before sending users to
/login. If those environment values are unset and no public forwarded host is
available, https://tuturuuu.com is the default fallback.
- macOS:
~/Library/Application Support/Tuturuuu/config.json - Linux:
$XDG_CONFIG_HOME/tuturuuu/config.jsonor~/.config/tuturuuu/config.json - Windows:
%APPDATA%\Tuturuuu\config.json
TUTURUUU_CONFIG to use a custom config
file, or use ttr host use <target> to target a local, production, or explicit
URL origin.
The CLI checks the npm registry at most once per hour and writes a notice to
stderr when a newer tuturuuu version is available. JSON invocations suppress
this notice on both output streams while retaining the update check and cache.
This covers --json and explicit JSON format/output flags; non-JSON commands
retain the notice. The CLI entrypoint regression tests in
packages/sdk/src/cli/update-entry.test.ts cover JSON success, feedback JSON
errors, and non-JSON notices. Use ttr upgrade to run bun i -g tuturuuu. Use
--no-update-check on a single command, or set TUTURUUU_DISABLE_UPDATE_CHECK=1,
to disable the check.
ttr upgrade install [version] keeps releases side by side in
~/.tuturuuu/cli/versions/<version>. It requires Bun even when the CLI runs under
Node. Registry requests have a two-minute deadline; archive bodies stay in memory
under a 64 MiB cap and must match sha512 dist.integrity before extraction.
Installation uses bun install --production --ignore-scripts. release.json
stores filesSha256, one SHA-256 hash over the ordered per-file path/hash lines,
and files, their count. It does not store the individual per-file hashes.
A mismatch publishes nothing; an invalid existing release is reported, never
overwritten. Running processes keep their own release files.
ttr upgrade activate <version> first runs the target release’s own
resources setup --root <first-root> --shell none --force --json, which refuses
while any resource owner or ticket is live, and only then atomically switches
~/.tuturuuu/cli/current, rewrites the ttr/tuturuuu/tutur3u launchers in
~/.tuturuuu/cli/bin and records the previous release. Roll back by activating
the previous version. ttr upgrade list shows verification state and
ttr upgrade prune --keep <n> never removes the current or previous release.
Verification is not an atomic directory snapshot against concurrent changes by
the same OS user. Pruning can remove a release still used by a long-running older
CLI process; retain that release until its processes finish. Windows filesystem
behavior is not qualified; unsupported secure-filesystem operations fail closed.
Unknown ttr upgrade subcommands, flags and surplus arguments fail with a usage
error before any installation. ttr upgrade list [--json] only reads releases.
Older published parsers can treat even ttr upgrade list --json as a global
installation. Before using upgrade subcommands on a shared host, identify the
installed release with ttr --version or its package.json and verify that it
supports them. Do not probe support by running an upgrade subcommand. Drain
active jobs before any global upgrade or activation.
Bare ttr upgrade installs npm latest, not main. Main’s SDK dev workflow
publishes qualified prereleases under npm dev; stable publication follows the
Release Please and production release workflow. Check publication using
npm view tuturuuu@dev version or the workflow result.
Regression coverage: cli-versions.test.ts, cli-versions-command.test.ts,
cli-versions-review.test.ts, cli-versions-download.test.ts,
cli-versions-security.test.ts, and cli-versions-fd.test.ts.
CLI sessions are dedicated Tuturuuu gateway JWTs, separate from normal browser
and Supabase Auth sessions. The CLI stores a short-lived app-session access JWT
plus a longer-lived refresh JWT, refreshes shortly before access-token expiry,
updates the saved config with the rotated refresh token, and retries once after a
401 response. Refresh JWTs only carry the CLI refresh scope, so they cannot be
used directly as API bearer tokens. If refresh fails because the user no longer
exists or the refresh JWT has expired, run ttr login again.
The SDK fetch wrapper must attach CLI bearer tokens, refresh sessions, and retry
401 responses only for relative Tuturuuu API paths or absolute URLs whose
origin exactly matches the configured CLI base URL. Cross-origin absolute URLs
must be delegated without Authorization headers.
API routes used by ttr must accept the CLI app-session access JWT from the
Authorization: Bearer ... header. For apps/web routes, prefer
withSessionAuth with targetApp: 'platform' and
requiredScope: 'cli:access' when the route is CLI-only; shared user profile
routes can include the platform target alongside satellite app targets. Shared
API routes should verify the CLI app-session token before falling back to
Supabase session auth and then keep workspace membership checks explicit.
When a handler lives in packages/apis and is reused by multiple apps, keep
@tuturuuu/auth imports in the app-owned route wrapper and pass a
pre-authenticated context into the shared handler so package builds do not form
an auth/UI/API cycle.
SDK version and changelog PRs are generated by
.github/workflows/release-please.yaml from Conventional Commits on
production. Release Please tracks the SDK in release-please-config.json as
the sdk component, records the current version in
.release-please-manifest.json, and updates packages/sdk/package.json in the
combined monorepo release PR. SDK releases are then published automatically from
.github/workflows/release-sdk-package.yaml when that version bump lands on
production or when the release workflow itself changes. The same publish
workflow can be dispatched manually to catch up a version that already landed on
production. Manual dispatches from any other ref are rejected before
dependency installation, packaging, or npm trusted publishing can run. The
workflow runs the SDK tests, skips publication if the exact package version
already exists on npm, waits for publishable Tuturuuu workspace dependencies to
be visible on npm, prepares a tarball with npm pack outside the
trusted-publish job, and then publishes that tarball through
npm trusted publishing. Only the
final publish-npm job requests id-token: write, so GitHub Actions mints the
short-lived OIDC token after dependency installation, package builds, and version
checks have already finished. That job downloads the prepared artifact, verifies
its package name and version, confirms the bundled npm CLI supports trusted
publishing, runs npm publish ./<tarball> --ignore-scripts, and polls npm view
until the exact SDK version is visible without using an NPM_TOKEN. npm treats
the downloaded artifact as a local file path. The job
targets the GitHub environment sdk-release-production; keep that environment
restricted to the production branch in repository settings, and keep the npm
trusted publisher bound to the same environment name so a branch-selected
workflow cannot mint a matching npm publish identity. Do not add bun install,
npm install -g, package builds, package lifecycle scripts, or repository
secrets to the SDK release workflow. Because the tuturuuu package is public
and tutur3u/platform is a public repository, npm also generates and attaches
build provenance attestations automatically. The package manifest inside the
tarball must keep repository metadata aligned with the provenance repository:
repository.url is https://github.com/tutur3u/platform, and
repository.directory points at the package directory.
.github/workflows/sdk-version-bump.yaml is retired and no longer generates
patch-version PRs. Do not restore checksum or package-only bump automation for
the SDK; release-please must remain the version/changelog source of truth.
The SDK imports @tuturuuu/devbox, @tuturuuu/types, and
@tuturuuu/internal-api through package exports that point at their git-ignored
dist/ directories. The release workflow must build those workspace packages in
the non-OIDC build and prepare jobs before running SDK tests or creating the
npm pack tarball; otherwise a clean Actions checkout cannot resolve the SDK
imports. The workflow then runs
node scripts/ci/prepare-npm-package-manifest.js packages/sdk before
npm pack so the published manifest contains concrete npm versions instead of
local workspace: protocol ranges. Keep matching npm release workflows for the
SDK runtime workspace dependencies so consumers can install the published SDK.
When release-please bumps the SDK and one of those dependencies in the same
production push, the SDK workflow waits for the dependency versions before it
packs and publishes. If a dependency release workflow for the same SHA fails,
the SDK wait fails immediately instead of waiting for npm visibility until
timeout.
The matching npm-side trusted publisher must stay configured at
https://www.npmjs.com/package/tuturuuu/access with these exact values:
- Provider: GitHub Actions
- Organization or user:
tutur3u - Repository:
platform - Workflow filename:
release-sdk-package.yaml - Environment name:
sdk-release-production
npm publish can fail with ENEEDAUTH, E404, or
permission text. Update the npm package access or trusted publisher entry first;
do not paper over the failure by reintroducing a long-lived NPM_TOKEN. After
confirming a trusted-publisher release works end to end, the npm package
settings should keep Require two-factor authentication and disallow tokens
enabled so the package cannot be published with classic tokens.
Dev channel and private previews
The stable channel remains Release Please plus the production release workflow. The dev channel builds each SDK change onmain, or a manual dispatch of
release-sdk-dev.yaml on main. It qualifies the portable package, publishes
X.Y.(Z+1)-dev.<run-number>.<sha7> under npm’s dev tag with provenance, and uploads
the tarball, qualification receipt and manifest.json. It never updates latest.
The manifest records the version, full source SHA, SHA-256, npm integrity and
shasum. ttr --version prints only the bare version for machine consumers.
ttr resources status --json reports the installed version, channel and source SHA. Older stable packages without build metadata report an
unknown source; do not infer a source SHA from their version.
Branch Actions builds are deliberately refused. This repository is public, so
Actions artifacts are not a private distribution boundary. For premerge work,
build a clean committed checkout privately on an admitted build host, then transfer the tarball
and manifest over SSH. Do not upload unmerged previews to public Actions artifacts
or publish them to npm. Checksums establish byte identity, not code approval.
Cut a build
For the main channel, dispatch the workflow after its operator setup below:internalOnly: true manifest and tarball private. The command builds the
portable SDK and dependencies, then runs the same public consumer/compiled-CLI
qualification and receipt verification as the official release. Missing registry
dependencies stop qualification; they are not replaced with workspace links.
The pack lock records its owner PID and gated child process groups atomically.
A later pack reclaims the lock after a crash or SIGKILL only when the owner and
every recorded process group have ended. Live builders are never evicted by age. If owner metadata is malformed or a reclaim guard
was interrupted, inspect packages/sdk/.dev-pack.lock and its .reclaim/owner.json
first. Only after confirming every recorded PID and process group has ended, remove those exact
lock/guard paths and retry. Do not delete a live owner’s lock or partial package
outputs from another job. These coordination files are ignored by Git.
Roll out and recover
The rollout tool is a repository command, so it can update an older global CLI. Run it outside an admitted job: its cutover needs an idle queue. Each target uses its existing home, global Bun installation, queue, roots and limits. Hosts run sequentially; the first failed or unknown result stops the fleet operation and lists never-attempted hosts as skipped. SSH receives the requested drain time plus a 30-minute operation budget. A disconnected attempt is reconciled from a private terminal report when available; otherwise retain itsreportFile and
staging paths for inspection. Do not replay an unknown installation.
--wait-drain, an occupied queue refuses immediately. A bounded
wait does not authorize interrupting another job.
Automatic rollout targets must advertise tuturuuuBuild.rolloutProtocol: 1.
The existing 0.28.0 package predates that protocol and is refused before install;
use the reviewed manual drain/backup procedure for that older target, or wait for
a maintenance-capable stable release. Newly qualified dev and stable packs carry
the protocol marker. Every rollout target requires Python 3, preflighted before
installation, for its OS-released recovery lock. The tool qualifies project root/package metadata, not each
project’s test suite.
Use the generated rollback command in the report to restore the saved supplier
without downloading it again. Rollback also requires an idle queue. Preserve the
backup until the host passes qualification; reconcile a failed or interrupted
rollback before attempting another rollout. Do not change TTR_RESOURCES_HOME,
link a workspace dist, or run concurrent global package-manager operations.
Global installation is required by the same-install trust fence.
A later rollback compares the current global install, configuration and launchers
with the recorded post-rollout hashes. Rollback refuses after another global
package or a root/limit change. Do not force a whole-install restore over newer
state; inspect and reconcile that state first. An interrupted rollback retains
its same-operation recovery fence so it can resume safely.
The automatic restore after a failed qualification compares the global package
tree with the installed-state receipt and reports rollback: "failed" with the
drift error instead of reverting a concurrent global install made after that
receipt. The guard does not cover a global install made before the receipt is
recorded (during install or setup), a failure before any receipt exists, or a
change between the check and the restore; those can still be reverted. Keep the
rollout’s exclusive use of the global package manager for the whole run. Launcher and
configuration writes during qualification are candidate defects and are still
restored. Report rollback describes only
restore work. Housekeeping failures (retention pruning, report writing) set
cleanup: "failed" with cleanupErrors and leave the outcome and admission
unchanged; only a changed slot or lease owner also sets admissionHeld: true,
fails the report and keeps admission closed. Do not run rollback.sh for a
cleanup-only failure.
Candidates install from the stable content-addressed cache at
~/.tuturuuu/resources/suppliers/<sha256>.tgz, separate from backups. Completed
rollouts prune old completed backups, retaining five recent backups plus the
current/protected backup. Failed or unknown recovery evidence stays retained.
Cache pruning preserves archives referenced by the current installation and
retained backups.
Rollout integrity checks classify and read launcher files, supplier file contents,
and cache manifests through the same open descriptor. Directory fingerprints
compare the traversed path’s device and inode with the open directory before and
after enumeration and after hashing children; a replacement aborts the check.
Cache manifests intentionally follow existing symlinks, while launcher paths
reject them. Special files fail without waiting on a FIFO.
Run node --test scripts/sdk-rollout-descriptor.test.js scripts/sdk-rollout-race.test.js
for deterministic check/read and directory-swap regressions. The test loader
injects filesystem hooks without changing the production functions. Set
SDK_ROLLOUT_TEST_REF=<git-ref> to exercise the race tests against historical
source; FIFO controls run in child processes with a two-second kill deadline.
These checks do not replace exclusive use of the global package manager during
rollout and restore.
Recover a stale admission fence
A dead preparation owner with noadmission/mutation.json has not changed the
installation. Preparation has no retained reclaim fence; ordinary admission
reclaims a dead preparation owner. If a legacy or malformed fence still blocks
admission, stop new submissions and inspect
~/.tuturuuu/resources/admission/owner.json. Verify the recorded owner PID and
child PID/process group have ended, the token is unchanged, and mutation.json
is absent. Preserve the owner record and move only that confirmed stale
admission/ directory into a private backup before admitting new work. Never
move a live owner’s directory or remove slots, queue tickets or legacy locks.
When mutation.json exists, keep admission closed and use the backup path
recorded there to run its rollback.sh. A missing, malformed or ambiguous record
requires operator investigation; do not clear the fence to bypass it.
Promote code by merging the reviewed change and following the existing
production/Release Please process. Do not promote a dev tarball by moving
latest: stable releases are separately versioned, qualified and published.
Operator setup and threat model
Before enabling publication, create GitHub environmentsdk-release-dev with
an explicit deployment branch restriction allowing only main. Add an additive
npm trusted publisher for tuturuuu: GitHub Actions, owner tutur3u, repository
platform, workflow release-sdk-dev.yaml, environment sdk-release-dev.
Keep the existing production publisher unchanged. Use GitHub-hosted publish
runners; do not add a registry token or secrets to artifacts. See
npm trusted publishers.
The trust boundary is reviewed main source, a nonprivileged build/qualification
job, and an isolated OIDC publish job that treats artifacts only as data. The
publish job has no checkout, builds, dependency installation or lifecycle scripts.
The environment restriction prevents a modified branch workflow from obtaining
matching publish authority. A checksum prevents corrupt or substituted bytes
when its expected value arrives through a trusted channel; it does not make
unreviewed code safe. Installing a private preview grants that code the host
user’s authority. Backups may contain private scheduler configuration: keep them
on the target with private permissions and never commit or upload them.
Devbox Setup
Usettr box setup on self-hosted runner machines before offloading expensive
checks, Supabase workflows, or E2E suites. The command clones or reuses
https://github.com/tutur3u/platform.git at ~/Documents/tuturuuu, runs
bun install --frozen-lockfile, starts the local Supabase stack with
bun sb:start, reads supabase status -o json, and upserts local Supabase
connection values into ignored apps/*/.env.local files with secret values
redacted from CLI output.
ttr box doctor stays read-only. Use ttr box setup --yes only when the host
should install detected missing prerequisites through its package manager.
Use ttr box repair --dir . after a CLI upgrade to repair an existing runner
service without registering a new runner token.
Machine Storage
ttr storage reports disk headroom and ranks what fills a machine by the bytes
deleting it would actually free (APFS private bytes on macOS, allocated blocks
with hard links counted once on Linux). prune is a dry run until --apply.
preserve/* branches, or any path listed in a hold file
(~/.t3/cleanup/holds.d/*.txt). See
Machine storage health for
thresholds, categories and per-OS commands.
Scoped Help
Use scoped help when you know the area but not the exact flags. Help commands do not require login, do not read the saved config, and do not run the update check.Agent-Friendly Discovery
For autonomous engineering workflows, start by discovering the current account and selected context:ttr whoami shows login state, account email, current workspace, selected
board/list/task/label/project ids, base URL, config path, and the Tuturuuu CLI
session label. Use --json when another agent is going to parse stdout;
--no-update-check is optional and skips the registry check entirely.
The v1 task surface covers workspaces, boards, lists, tasks, labels, projects,
relationships, moves, and bulk task updates. Read-oriented groups list by
default, so ttr tasks and ttr workspaces are equivalent to their explicit
list forms. ttr boards shows active, unarchived boards by default; use
ttr boards --archived, ttr boards --deleted, or ttr boards --all when an
agent needs archived or deleted board inventory. Unscoped ttr tasks starts
from the personal workspace and lists personal tasks plus open tasks assigned to
the user in other accessible workspaces; selecting a board/list or passing
--workspace scopes the result back to that context. ttr tasks shows open
tasks by default by excluding rows with completed_at or closed_at and by
limiting task-list statuses to not_started and active. That default hides
tasks in documents, review, done, and closed lists, and tasks from
archived boards; use --all, --include-archived, --documents, --review,
--done, --closed, --include-documents, --include-review,
--include-done, or --include-closed to adjust that filter. Results are
paginated at 50 tasks by default; use
--page/--page-size or --limit/--offset to page through larger task sets.
Human-readable task lists show a footer with the total task count and current
page/max page. Add --compact to task lists when an agent only needs the task
title, task list name, and per-task workspace name. Task lists are ordered by
priority and due date, with prettier due dates and configured task-list colors
in table output. Human-readable tables use smooth box borders, respect the
terminal width, and wrap long cell content so task titles stay readable instead
of overflowing narrow panes. Add --json
when another agent or script needs machine-readable output.
Use ttr tasks search <query> for ranked task search. Search defaults to
--mode hybrid, accepts --mode text for PostgreSQL full-text search only and
--mode semantic for embedding similarity only, and supports
--query/--q, --limit/--match-count, --threshold/--match-threshold,
--workspace/--ws, --compact, and --json. Search output preserves API
relevance order and shows the score when available. Existing list filtering
with ttr tasks --q <query> is unchanged and remains the lightweight list text
filter.
tasks create, boards create, and lists create accept a quoted positional name as
a shorthand for --name. Task CRUD accepts either the task UUID or the board
identifier shown in the UI, such as VHP-12; prefixed identifiers resolve
within the selected workspace even when another list is selected, and matching
human-readable output keeps that prefixed identifier visible. Marking a task
completed stamps completed_at so Tuturuuu moves it to the first done list;
pass --list <done-list-id> or include list_id in --json-payload to choose
another done destination. Use ttr tasks done [task-id] as the quick shortcut.
Use ttr tasks close [task-id] to stamp closed_at; pass --list <closed-list-id> to choose a specific closed destination.
Task templates are single-task starters, separate from board templates.
Workspace templates are managed with
ttr task-templates list/show/create/update/delete/use and can be imported
from or exported to local markdown files. Local templates live under
.tuturuuu/task-templates/*.md, use YAML frontmatter for fields such as key,
name, task_name, priority, label_ids, assignee_ids, and
project_ids, and use the markdown body as the task description. Markdown
descriptions from --description-format markdown and local template bodies
support GFM pipe tables and are stored as TipTap/Yjs table content for the web
editor. ttr tasks create --template <key-or-path> resolves workspace keys or
local markdown paths; explicit flags such as --name, --list, --priority,
--labels, and --projects override template defaults.
Finance CLI
Finance commands use the selected workspace and the same authenticated internal APIs as the web app. Use--workspace or --ws to target a different
workspace, and use --json-payload when a script needs to send fields that do
not have dedicated flags yet. Finance list output is paginated by default; use
--page/--page-size or --limit/--offset to move through larger result
sets.
Finance API routes that back ttr finance should resolve access through
getFinanceRouteContext from @tuturuuu/apis/finance/request-access, with
app-owned routes passing resolveFinanceRouteAuthContext(request) from
apps/web/src/lib/finance-route-auth.ts when they need CLI app-session bearer
token support. Keep CLI token verification in the app layer so @tuturuuu/apis
does not depend on @tuturuuu/auth; getFinanceRouteContext still keeps the
pre-authenticated actor, browser Supabase sessions, personal workspace
resolution, and finance permission checks on the same path.
Calendar CLI
Calendar commands use the selected workspace and the same authenticated calendar APIs as the web app. Use--workspace or --ws to target a different
workspace, and use --json when scripts need the raw structured response.
Human output renders resource-specific tables for events, calendars, categories,
accounts, provider calendars, connections, sources, and schedulable tasks.
Event time input is exact ISO datetime only. events create requires --start
and exactly one of --end or --duration-minutes; events update --duration-minutes also requires --start. Source flags map to provider
payloads: use --source-provider tuturuuu --calendar <workspace-calendar-id>
for workspace calendars, or --source-provider google|microsoft --connection <connection-id> for connected provider calendars. calendar calendars reset
is destructive and requires --yes.
use, get, update, delete, or
move commands to pick a workspace, board, list, task, label, or project with
the keyboard. The interactive picker shows one-based indexes, colored badges
such as [FREE] Tuturuuu and [PRO] Personal, task-list color swatches, the
selected row, and muted metadata. Use up/down or j/k to move, space/enter to
select, and escape/q to cancel. Interactive selection is disabled for --json
output. Picker labels, descriptions, badges, and color names derived from
platform data must visibly escape terminal control characters before rendering.
Use ttr -v or ttr --version to print the installed CLI version.
Client Initialization
Basic Usage
With Custom Configuration
Storage API
List Files
Lists files and folders in the workspace drive.Upload File
Uploads a file to the workspace drive.Download File
Downloads a file as a Blob.Delete Files
Deletes one or more files or folders.Create Folder
Creates a new folder in the workspace drive.Share File
Generates a signed URL for temporary file sharing.Get Analytics
Retrieves storage usage statistics for the workspace.Documents API
List Documents
Lists documents in the workspace with optional filters.Create Document
Creates a new document in the workspace.Get Document
Retrieves a document by its ID.Update Document
Updates an existing document.Delete Document
Deletes a document permanently.Search Documents
Searches documents by name (alias forlist() with search).
CMS Delivery
The SDK also exposes the external-project delivery client that powers Tuturuuu CMS preview surfaces and branded external apps.artworkCategories comes from the configured gallery taxonomy on singleton-sections/gallery (profile_data.categoryOptions) rather than being inferred from every artwork entry.
External app admin dashboards can auto-create and bind the workspace studio before their first diff/apply when the workspace ID is configured and the app token has external-projects:* or manage scope:
Public Folder Asset Sync
Register and configure an external site from the CLI
Root operators can manage registered apps, approve scopes, create templates, and bind workspaces without opening Infrastructure or CMS. Existing root and workspace permission checks remain mandatory; the CLI does not grant access.id, displayName, enabled, origins, allowedScopes,
and allowedWorkspaceIds (explicit UUID allowlist). Optional requirePkce and
callbackPaths configure PKCE-bound handoffs;
omitting them keeps the stored values. Do not include secrets or
audit fields.
Rollout rule: Never set requirePkce=true for any app until migration
20261101090000 is applied in production; record it in the release receipt.
Until then generate_cross_app_token_pkce and
validate_cross_app_token_with_session_pkce do not exist, so every login for
that app would fail.
Save updates these fields without rotating credentials. Scope
changes replace the whole approved list; --clear-scopes revokes all scopes.
Custom template JSON can be
{"id":"rennu","display_name":"Ren Everheart","adapter":"custom"};
the template ID must match the registered app ID. Run projects setup, diff,
and confirmed apply after binding to configure and populate the workspace.
0600) file, never the secret to stdout. Transfer it to
the app’s server environment and securely remove the local file; never commit
it. A failed request may leave an empty file, which is not overwritten on retry.
The SDK equivalent lives at TuturuuuUserClient.external.admin.
External apps can keep seed media in their local public/ folder and still publish it through Tuturuuu Drive. Mark each manifest asset with metadata.publicPath, metadata.localAssetPath, metadata.sourcePublicPath, or a relative sourceUrl.
external-projects/{adapter}/{collectionSlug}/{entrySlug}/{filename}. External apps can upload those assets through signed upload URLs, or through the external-project asset multipart route when they need the app server to stream the file with a server-owned bearer token. Production Supabase keys are not needed in external apps.
TanStack Query
For React apps, keep the SDK as the delivery boundary and TanStack Query as the cache owner:Error Handling
The SDK provides specific error classes for different scenarios:Error Properties
All error classes include:message- Human-readable error descriptioncode- Machine-readable error codestatusCode- HTTP status codename- Error class name
TypeScript Support
The SDK is fully typed with TypeScript:API Key Management
Creating an API Key
- Log in to your Tuturuuu workspace
- Navigate to Settings → API Keys
- Click “Create API Key”
- Set a descriptive name
- Assign a Workspace Role (determines permissions)
- Optionally set an expiration date
- Copy the generated key (starts with
ttr_) - Store securely (you won’t see it again)
Best Practices
Permissions
API keys inherit permissions from their assigned Workspace Role. The role determines what operations the key can perform. Required Permissions:
Note: Permissions are managed through workspace roles, not individual API keys. To modify what an API key can do, update its assigned role’s permissions in Settings → Roles.
Security Best Practices
❌ Insecure (NEVER do this)
✅ Secure (Recommended patterns)
Next.js App Router (Recommended)
Create server-side API routes that proxy SDK calls:Node.js/Express Backend
Serverless Functions (Netlify, Vercel, AWS Lambda)
Security Checklist
✅ DO: Use server-side code
✅ DO: Use server-side code
Always use the SDK in server-side contexts:
- Next.js API Routes (App Router or Pages Router)
- Node.js backend servers
- Serverless functions
- Server Components (React Server Components)
✅ DO: Use environment variables
✅ DO: Use environment variables
Store API keys in environment variables:Never use
NEXT_PUBLIC_ prefix for API keys!✅ DO: Add .env.local to .gitignore
✅ DO: Add .env.local to .gitignore
Ensure secrets never get committed:
gitignore # .gitignore .env.local .env*.local ❌ DON'T: Use NEXT_PUBLIC_ prefix
❌ DON'T: Use NEXT_PUBLIC_ prefix
This exposes the variable to the browser:
bash # BAD - Exposed to client! NEXT_PUBLIC_TUTURUUU_API_KEY=ttr_key ❌ DON'T: Import SDK in client components
❌ DON'T: Import SDK in client components
Never import the SDK in files with
'use client' directive.❌ DON'T: Hardcode API keys
❌ DON'T: Hardcode API keys
Never commit keys to version control:
Complete Example
See the external app example for a complete implementation showcasing:- ✅ Secure server-side API routes
- ✅ File upload with progress
- ✅ Storage analytics dashboard
- ✅ Proper error handling
- ✅ Environment variable configuration
Rate Limiting
The API enforces rate limits per API key to ensure fair usage and system stability:Rate Limits by Operation
Rate Limit Headers
Every API response includes rate limit information in the headers:X-RateLimit-Limit- Maximum requests allowed in the time windowX-RateLimit-Remaining- Number of requests remaining in current windowX-RateLimit-Reset- Unix timestamp when the rate limit resets
Handling Rate Limits
When you exceed the rate limit, the API returns a429 Too Many Requests response:
Workspace-Specific Rate Limits
Workspace administrators can configure custom rate limits for their workspace through Workspace Secrets. These override the default limits for all API keys in that workspace. To set custom rate limits:- Navigate to Settings → Workspace Settings → Secrets
- Add one or more of these secrets:
Example Configuration:
Workspace-specific rate limits only apply to operations that don’t have
explicit per-operation limits. For operations like uploads and downloads, the
operation-specific limits still apply.
Best Practices
Examples
Basic Usage
Complete example of all SDK operations
Error Handling
Comprehensive error handling patterns
Support
GitHub
Source code and issues
Discord
Community support
Docs
Full documentation
Changelog
The package version is managed by Release Please, and the full, authoritative changelog is generated per release. For the complete history (currently shipping the0.10.x line), see the maintained changelog rather than this page:
CHANGELOG.md
Full per-release changelog (source of truth)
npm
Published versions and release tags
- Storage operations (list, upload, download, delete, folders, share, analytics)
- Document operations (CRUD, search)
ttrCLI with workspace, host switching, and session-based auth- Finance commands (wallets, transactions, transfers, tags, wallet checkpoints)
- Calendar commands
- External projects support and remote devbox tooling
- Comprehensive error handling, full TypeScript types, and Zod validation
Do not treat any version number quoted in this page as exact — the SDK is on a
fast release cadence. Always check the linked changelog or npm for the current
version.
License
MITLocal resource control
Usettr resources to keep coding sessions responsive with bounded validation on the same machine. This opt-in feature supports macOS/Linux without login or a background daemon.
run requires setup-provisioned executable shims matching the current runtime and resource entry. It only reads and validates those launchers; it never installs, rewrites, chmods, or activates them. Missing, stale, malformed, or nonexecutable shims fail before queue admission and workload launch, with an explicit ttr resources setup --shell none instruction. Inspect occupancy and obtain installation authority before setup; a source update does not authorize replacing an installed scheduler. Resource commands dispatch before general authenticated CLI imports. This avoids those eager imports but does not establish the cause or repair of any host-specific startup hang. Regression coverage lives in resources-provisioning.test.ts and entry-dispatch.test.ts; existing lane tests retain FIFO, pressure, and owned cancellation/drain coverage.
Setup includes linked Git worktrees. One canonical per-user FIFO queue defaults to one lane; missing validationLanes in version-1 configurations means one. validationLanes is the base lane count, an integer from 1 to 8, configured with setup --lanes. --workers sets the aggregate Vitest/Cargo total. Configurations split that total across lanes with rounding down, but every lane receives at least one worker. Turbo runs one task per lane, and explicit Vitest/Cargo flags are capped. --dry-run inspects changes without writing.
Admission is reentrant. An admitted job exports TTR_RESOURCES_ADMITTED=<owner token> (and the legacy TTR_RESOURCE_OWNER). A nested ttr resources run or shim-admitted heavy command inside that job runs immediately with the owner’s caps instead of queueing behind its own ancestor. The marker counts only while it names a live owner inside that owner’s process tree (an ancestor PID or the owned launcher’s process group). When ps ancestry is unavailable, the marker cannot be verified, so it is ignored with a warning and the command queues. On Linux without a trusted ps, lineage falls back to the kernel’s /proc/<pid>/stat, which is not PATH-controlled. Where no ancestry evidence exists at all, a waiter whose marker names a live owner fails after 60 seconds with an actionable error (install ps, or use TTR_RESOURCES_BYPASS=1) instead of waiting forever behind its own ancestor (resources-ancestry.test.ts). Ancestry and process-group liveness come only from /bin/ps or /usr/bin/ps, run with an empty environment; ps is never resolved through the caller’s PATH, so a fake ps cannot fabricate ancestry. Without a trusted ps there is no evidence and the command queues. Stale or copied markers are likewise ignored with a warning and queue normally. Children whose environment is filtered, such as under Turbo strict env, are matched by ancestry alone. Successful ancestry snapshots are cached per process, and failed ps probes are retried. A waiter also rechecks ancestry about once per second, so a transient probe failure cannot deadlock it behind its own ancestor. Regression coverage lives in resources-reentrancy.test.ts.
Explicit run skips the queue only for a few read-only coreutils (echo, pwd, true, false, whoami, uname, date, printenv, cat, head, tail, wc). ls always queues because recursion hides in bundled flags (-1R) and GNU long-option abbreviations (--rec). The exemption is decided on the executable that would actually run: the first PATH match (or the given path) must resolve to the same-named binary directly inside /bin, /usr/bin or Ubuntu’s rust-coreutils applet directory /usr/lib/cargo/bin/coreutils (exact directories, no subdirectories; the same basename keeps a multicall binary on the intended applet, so busybox-style targets queue), and that file and every directory above it must be root-owned and not writable by group or others, so an impostor script named cat or /any/path/node queues. On Ubuntu 26.04 (a Linux build host, verified 2026-10-23) /bin links to usr/bin, and /usr/bin/{cat,echo,head,tail,wc,pwd,date,uname,printenv,whoami,false} link to root-owned 755 applets in /usr/lib/cargo/bin/coreutils, with every directory up to / root:755. Those tools are trusted there; true links to /usr/bin/gnutrue, a different basename, so it queues. The host-gated test in resources-trusted-path.test.ts rechecks this on any such host. The CLI then spawns exactly that canonical path, never the original name or symlink, so swapping a symlink after classification cannot run a payload (resources-race.test.ts; Linux and macOS layouts in resources-trusted-path.test.ts). It is also refused while LD_*, DYLD_*, GCONV_PATH or LOCPATH is set. Runtimes and package managers queue even for --version/--help, because NODE_OPTIONS=--require, npm_config_* and preload settings load code before any flag is read. Every git command queues, because status/ls-files run core.fsmonitor (also injectable through GIT_CONFIG_COUNT), branch runs the reference-transaction hook and a pager, and diff|log|show run textconv or external diff drivers. Regression coverage lives in resources-hardening.test.ts and the occupied-slot dispatcher test in resources-bypass.test.ts. Shims already pass non-heavy commands straight through.
Node shim classification consumes known runtime option values, including --diagnostic-dir /tmp, before identifying a tool entrypoint. Unknown or incomplete Node options queue conservatively, even when later arguments include --help. Recognized Vitest entrypoints retain their worker cap after runtime options and the Node -- separator. Arguments after an ordinary script path remain that script’s arguments. The Node 26.4 bare forms of --experimental-config-file and --experimental-specifier-resolution do not consume the next argv; only their = forms are accepted as value options. Any separate option value that itself identifies a tool, ttr launcher, or heavy script makes classification ambiguous and queues conservatively.
The adaptive design requires a CLI release before activation. Optional maxValidationLanes accepts an integer from the base count through 16 and defaults to the base count. After release and a full idle drain, ttr resources setup --max-lanes N explicitly opts into a higher ceiling. Host capacity never changes the default automatically. Changing base lanes, maximum lanes, or aggregate worker budgets requires idle owners and zero waiters. Use --workers only for a justified aggregate budget. A maximum is a ceiling, not a promise of concurrent jobs.
Burst admission above the base requires a load-to-CPU ratio below 0.7 and sufficient memory. macOS requires normal memory pressure, reported as 1. Linux requires at least 25% available memory and enough available memory for the estimated per-lane need. Missing telemetry refuses burst admission while preserving base admission behavior. Burst memory checks still apply with --ignore-pressure. Waiters with readable telemetry keep FIFO order; unreadable burst telemetry leaves that waiter base-only as described below. Running jobs are never interrupted to shrink occupancy.
Shims classify only the program for node, bun, bunx and npx: the first positional argument after the runtime’s own flags. Its later arguments belong to that program, so bun /path/ttr resources run -- bunx vitest is ttr, not vitest. The ttr CLI is a #!/usr/bin/env bun launcher and starts through the bun shim whenever the shims are on PATH (for example from a ~/.zshenv that adds them for every zsh, including ssh host 'cmd' and zsh -c). Shims never admit it: ttr performs its own admission, and a shim admitting it first would hold the only lane while ttr run queued behind its own parent. Inside an admitted job, nested shims (bunx → a #!/usr/bin/env node vitest entry → the node shim) pass through under the owner once ancestry is verified. resources-shim-chain.test.ts reproduces that chain with one lane.
The effective lane count is max(1, min(maxValidationLanes, host lane ceiling)), so base lanes above the host ceiling (for example a config copied from a larger machine) are capped as well, where the host lane ceiling is max(1, min(16, floor(memory GiB / 4), CPUs - 2)) (6 on a 24 GiB / 10 CPU host). Worker totals no longer cap the lane count. Admission subtracts recorded running allocations from each budget. Once the aggregate budget is spent, each further pressure-gated burst lane receives exactly one worker, so the overcommit is bounded to one worker per burst lane. For multi-lane or adaptive configurations, the aggregate worker total is clamped with a warning to the host worker ceiling, min(8, floor(memory GiB / 8), floor(CPUs / 2)) when that exceeds the legacy budget. Configured totals are never raised automatically.
Before replacing the installed scheduler, fully drain old brokers, shim processes, and FIFO waiters. An old scheduler encountering slots/2 fails closed. Never rewrite tickets, remove live locks, or run mixed versions. Source tests/review do not authorize installation or activation, and this design does not change any installed scheduler. Reconcile every legacy status.owner consumer first. Status retains owner, owners, laneCount, waiting, and limits, and adds baseLanes, maxLanes, effectiveLanes, occupied, burst: { eligible, reason }, ownerDetails (PID, child PID, command, cwd, elapsed seconds, current CPU percent, cumulative CPU seconds, idle seconds), waitingDetails (command and wait time), and warnings. Brokers sample their owned process tree’s cumulative CPU every 30 seconds into the slot’s activity.json. Status warns when an owner has been idle at 0% CPU for more than 10 minutes. It never signals or kills owners. The compatibility owner stays populated whenever any owner exists; initializing occupancy is reported separately.
Admission publishes complete owner metadata exclusively and serializes pressure checks, FIFO selection, and atomic slot claims, re-caps the original arguments to the admitted policy, and rejects policy changes during pressure probing. A live legacy owner, surviving child group, or old waiter excludes all slots. Malformed/unknown state fails closed. Initialization has a 30-second grace followed by a state recheck; time never expires a live owner. An abandoned mutex removal marker requires operator recovery review. Cancellation removes only its own ticket/slot after its owned child group drains. Workload execution waits for a durable child-PID receipt.
Cross-machine offload goes through ttr box. ttr resources run --offload auto -- <command> offloads only when every effective local lane is occupied or local waiters exist; --offload <runner-id> always targets that runner and fails instead of falling back. Offload requires a ttr login session and a clean tree whose HEAD is contained in an origin/* remote-tracking branch (a commit only on a fork or other remote is refused). Only the SHA, the relative directory and the argv travel, inside a typed commit-run envelope; no local environment is sent. The runner fetches that exact commit into a temporary worktree and removes it afterwards. Logs stream while the run polls, paging forward by the per-run event sequence cursor so long runs are neither truncated nor refetched, and lines that commit late are never skipped, the remote exit code is propagated, and --timeout (default 30m) or Ctrl-C (handled from before the run is created) requests a remote stop (exit 124 or 130). Each create carries a fresh operation ID as idempotencyKey. If the acknowledgement is lost, the CLI stops the run by that key within a bounded retry, or prints the key when cancellation cannot be confirmed. Once a remote run exists, the command never falls back to a local run in either mode: if the control plane stays unreachable while following it, the CLI requests a remote stop and exits 1. --offload auto queues locally only when no run was created: offload was unavailable, no runner was eligible, or the server rejected the run with a 4xx. An ambiguous creation failure (network error or 5xx) exits 1 instead, because the run may exist.
A runner is eligible only when its heartbeat is fresh (under 60 seconds by the server clock from the snapshot’s Date header, never the local clock; a list status of online alone is not freshness), it is not draining, run is enabled, it holds no Judge or playground role, it attests executionMode: trusted, and it advertises protocols.commitRun containing the client’s envelope version (capability_missing otherwise). Otherwise the diagnostic names a blockedBy reason: local_saturated, no_eligible_runner, offline, stale_heartbeat, draining, capability_missing or trust_cap. When no runner is eligible, the closest actionable blocker is reported first (stale_heartbeat, offline, draining, capability_missing), then trust_cap. Queue messages print blockedBy=local_saturated with an --offload auto hint. ttr resources offload [--runner <id>] [--json] shows local saturation, every runner’s eligibility and the selected target. ttr box runners --json adds the same trustedWork verdict per runner. Root admins can use ttr box feature --runner <id> --feature <name> --enable|--disable and ttr box drain --runner <id> [--undo]. Regression coverage lives in resources-box-offload.test.ts, resources-box-offload-safety.test.ts and devbox-fleet.test.ts.
Elevated memory pressure holds new admission while existing jobs continue. Blocked waiters back off outside admission, remain cancellable, and probe pressure afresh before launch. This is cooperative scheduling; scripts can still fan out within a slot. Sequence lint, tests and browser QA when memory is limited.
For turbo run commands (turbo, bun turbo:local, bunx turbo, bun x turbo, bun run turbo), run and turbo shims add --summarize=true and print one line after the command exits, for example turbo cache: 12/15 tasks cached (local 10, remote 2); 3 executed in 41s; saved ~4m10s. The spawn hook snapshots .turbo/runs/ after queueing. The report reads only a single new JSON file whose run ID matches its basename, and removes it only when this command requested it. Files present at spawn are always preserved. A user-supplied --summarize is reported but kept. Dry runs, --graph, other commands and runs without a Turbo root are untouched. Concurrent runs may share .turbo/runs/; their files are never selected by timestamps. If the command never spawned, or zero or multiple new JSON files exist, nothing is reported or removed. Child stdio stays inherited and completion follows process exit, so escaped background processes holding stdout cannot hold the lane. The report never changes the exit status. Regression coverage lives in resources-turbo-summary.test.ts and resources-summary-lifecycle.test.ts.
Shell setup preserves existing content and original backups through marked Zsh/Bash blocks. Open a new shell to activate hooks, or use --shell none for explicit queue usage. Absolute paths, custom PATH replacement and existing processes can bypass shims; wrap unrecognized scripts explicitly.
Out-of-range numeric configuration values (lanes 1–8, workers and Turbo 1–64, maxValidationLanes below the base) are clamped with a stderr warning that names the field and allowed range. Invalid JSON or wrongly typed fields produce an Invalid resource configuration: <field> <expectation> error that never discloses field values. Explicit run and setup fail on that error. status still prints its report with config.state: invalid and the error as a warning, then exits 1; it also reports disabled admission and base lanes (or, failing that, maxValidationLanes) above the host lane ceiling as warnings. Shims print it and run the command without admission, so one bad value cannot brick every tool on the machine. disable/enable toggle future automatic admission. All configuration mutations, including uninstall, serialize with admission and read the latest policy inside the mutex. Disabled, out-of-scope and deliberate bypass commands retain their arguments/environment without admission-cap validation. TTR_RESOURCES_BYPASS=1 is a deliberate one-command bypass. Once owners and waiters drain, uninstall removes owned hooks/shims and preserves shell content, configuration and backups. Keep the default TTR_RESOURCES_HOME across projects; private temporary homes are for lightweight synthetic tests, not parallel heavy work. Resource fixtures mock host capacity in Vitest parents and disposable Bun loaders/shims. Read exit diagnostics after subprocess pipes close; use a separate exit barrier when testing surviving descendants. Parent Vitest coverage is not evidence of coverage inside Bun subprocesses.
Multi-host dispatch fixtures in resources-hosts-concurrency.test.ts and
resources-hosts-auto.test.ts use fixed server/heartbeat timestamps, explicit
environments, injected disk inputs, and promise barriers for concurrent runs.
Separate wall-clock reads can put a heartbeat after its server timestamp and
refuse dispatch. Fixture admission polls complete immediately; real ledger
reservation checks and mutex backoff remain. A producer that exits before its
start barrier fails the test directly. The last-lane assertion still requires
exactly one remote dispatch and one local fallback, without a larger timeout.
The bounded monitor emits JSONL memory-pressure, T3-tree resident-memory and top-process samples without arguments or environment values. RSS differs from Activity Monitor’s footprint and can count shared pages multiple times.
Offload full validation with ttr box run --runner <remote-id> -- <command> only after verifying a different machine and intended checkout. Same-machine runners/containers consume local RAM; resource setup does not configure runners, transmit credentials, or restart Docker.
Cross-project budget on a shared host
A shared host (for example a build Mac shared by two projects) can carry abudget block in config.json. It is a layer on the existing lane admission, not a second scheduler: tickets, owners, the resources home and the admission mutex are unchanged. The budget decides which waiter may claim the next lane, and how many worker tokens that job may fan out to.
- The ceiling is
lanes/tokens/memoryGiB, with lanes up to 16. - Shares must fit inside the ceiling, and
maxValidationLanesmay not exceedbudget.lanes. - An invalid budget makes the whole config invalid: explicit runs fail, and shims print the error and run unmanaged.
- A project is the longest matching root, checked for the working directory and for a linked worktree’s main checkout (from
.git→gitdir→commondir). So every Upskii worktree under~/.t3/worktrees/upskiicounts against Upskii. - Work outside every root is
other: it has no share and only borrows idle capacity.
- Each ticket declares tokens at enqueue.
run --tokens NorTTR_RESOURCES_TOKENS=Nset them explicitly; otherwise the classified tool decides:vitestcosts its--maxWorkersvalue, orvitestWorkers;- Playwright costs its numeric
--workers/-jcount, or at least 2 when omitted or expressed as a percentage; database suites (supabase start|test|db reset,sb:*) cost at least 2; - builds (
next build,cargo build|check|clippy, Turbo or Bunbuildtasks) cost at least 4; - Turbo test and lint tasks,
tscandeslintcost at least 2; - anything else costs 1.
- Ambiguous runtime invocations cost at least
max(2, vitestWorkers)rather than one token.node --testreceives--test-concurrencymatching its charged tokens; existing concurrency flags are replaced only in the Node option segment, preserving program arguments. - Declared tokens are clamped to the ceiling.
- Admission then grants exactly that many workers (
VITEST_MAX_WORKERS,CARGO_BUILD_JOBS, and rewritten--maxWorkers/--jobs/--workers). Turbo stays at one task per lane, so a job’s internal fan-out is what it paid for. - Playwright always receives an explicit numeric
--workerscap. It overrides both CLI values and config defaults; percentage values fall back to the charged default rather than expanding with host CPU count. - Memory is declared with
--memory-gib/TTR_RESOURCES_MEMORY_GIB, and defaults totokens × memoryGiB / tokensof the ceiling (about 13.3 GiB per token on Studio).
- Each project keeps FIFO order, and only its oldest ticket competes.
- With
borrow: false, demand and worker caps clamp to the project share as well as the host ceiling. A request outside configured project roots, or with no positive lane/token/memory share, fails before enqueueing with guidance to use a configured root or enable borrowing. It cannot block the FIFO forever. - In-share heads reclaim capacity first, least-used share first. Ordinary borrowers yield to reclaim.
- A head that cannot fit even its empty project share gets age priority over younger in-share heads. If necessary, it reserves capacity and waits for running work to drain; younger small requests cannot refill that reservation. Older in-share heads retain reclaim priority. This includes unassigned
otherborrowers when borrowing is enabled. - If a reclaiming head fits its project share but cannot yet fit the host, new admissions stop until enough capacity drains. Smaller borrowers cannot continually refill its reserved capacity.
- Running jobs, borrowers included, are never preempted.
- Nested commands inside an admitted job still run under its owner (verified ancestry), so reentrancy is unaffected.
ttr resources status --json adds budget:
ceiling,usedandborrow;- per project:
share,used,borrowing(use above share) andwaiting.
resources-budget.test.ts (fairness, borrowing, reclaim, token and memory caps, project identity for worktrees, and a sustained mixed-load simulation) and resources-budget-admission.test.ts (real processes).
Process identity recovery remains tracked in #6561. PID/PGID liveness alone can retain a stale lease after PID reuse. Fix #6561 before enabling budgets on long-uptime Studio hosts: a reused PID can retain a dead owner’s reservation and stall every project. The budget fixes do not resolve that prerequisite.
Studio rollout: 0.28.0 single slot to the budget release
Run this only when Mastermind and Upskii have agreed a quiet window. The preparing agent does not change Studio’s live config. Use the single budget protocol rollout and rollback procedure below; install through the owning Bun global path, preserving existing roots and shell settings. A separate npm-prefix install is a different owner and cannot refresh the global launchers. After upgrading every client and launcher, write the proposed config atomically while idle (temporary file plus rename, mode 0600), keeping its backup:- preserve both projects’
rootsandrepositories; turboConcurrency: 1,vitestWorkers: 2,buildJobs: 4;- start at
validationLanes: 2, withmaxValidationLanes: 12; - use the budget above: 12 lanes / 24 tokens / 320 GiB, with 6/12/160 shares and
borrow: true.
~/.bun/bin/ttr resources run --tokens 1 -- true, then inspect resources status --json. Decide how runner work is counted: explicit admission or the shim directory on its service PATH. Direct SSH work and other unmanaged fan-out are invisible to the budget; inspect them separately. Preserve the saved shell policy: .zshenv also runs in noninteractive zsh, so it must be neutralised during rollback before the fence is removed.
Ramp the base 2 → 4 → 6 → 8 → 10 → 12 only through idle, atomic config updates; setup --lanes and --max-lanes accept 1 to 16 (maxResourceLanes). Bursts above the base still require pressure and load eligibility. Before each step, measure at least 15 minutes of normal load: normal memory pressure, one-minute load below 0.7 × CPU count, at least 30% idle CPU, budget use below its ceiling, and no regression in a fixed probe suite. Count unmanaged runner/SSH fan-out as well. More lanes are a ceiling, not a reason to start heavy work. For any downgrade, follow the canonical rollback below rather than removing the marker first.
Burst telemetry and minimal PATH
Resource admission and status probes use trusted absolute system executables:/usr/sbin/sysctl with /sbin/sysctl fallback, and /bin/ps with /usr/bin/ps
fallback. Probes have bounded time/output and a clean locale-only environment;
they do not resolve caller PATH binaries or inherit loader variables. Linux
pressure remains an absolute /proc/meminfo read. A minimal PATH can therefore
use macOS burst admission without adding /usr/sbin.
Unreadable load or pressure telemetry makes that ticket base-only for its
remaining wait. It retains its original owner, queue age and place for base
capacity. When only burst capacity is available, selection skips only these
base-only tickets and applies existing FIFO or project-budget ordering to the
remaining waiters. A real high-load or elevated-pressure reading still holds
the queue under the existing policy. No telemetry error grants a burst lane.
Cancellation, liveness, admission mutex and worker-cap checks are unchanged.
Regression coverage: resources-adaptive.test.ts reproduces the minimal-PATH
failure, retains the full-PATH control and high-load FIFO test, and exercises
an unreadable head followed by a healthy burst waiter. macOS cases execute the
real probe with deterministic pressure output and fixed host capacity/load;
Linux CI skips those OS-specific cases. resources-system-probes.test.ts
checks trusted paths, fallback, clean environment and bounded failure.
An operator applied a lane change on 2026-10-25: one pool A at 6 base/8
max, physical ceiling 16; pool B was not created. The Linux runner now has 3 lanes. This
records the operator change and does not request another drain-window raise.
The host’s private runbook holds the operational detail. Read
current status for worker caps; a declared total is not proof of effective
admission caps. Future policy changes remain operator-owned and require an
idle queue, preserved roots/settings and capacity evidence.
Multi-host weighted admission
ttr resources run --hosts -- COMMAND admits a job across the hosts in <resources home>/hosts.json. Without that file, --hosts refuses to run and admission stays the single-host lane FIFO. The CLI never writes or activates hosts.json; an operator installs it after a reviewed release. The registry decides where code runs, so it is trusted only from the canonical per-user home ~/.tuturuuu/resources. That directory must be a real 0700 directory owned by you, hosts.json a real 0600 file owned by you, and every parent a non-symlink directory writable only by root or you (a root-owned sticky directory such as /tmp is allowed). The file is opened with O_NOFOLLOW and re-verified through the descriptor. A home inside an opted-in repository root (roots, or a repositories checkout) is refused too, as is an invalid config.json. When TTR_RESOURCES_HOME points anywhere else, or any check fails, the registry is ignored and --hosts is disabled (resources-hosts-trust.test.ts). ttr resources hosts [--json] shows each host’s budget, reservations and waiters (or the proposed registry when none is installed). ttr resources hosts --simulate [--json] is an offline dry run of the measured queue through the same planner.
-
Classes and weights. Explicit
--hostsjobs areheavyunless--class lightis given; trivial commands never queue. Local light work also passes the dequeue disk gate, and the CLI tells a nestedrun --hostswhich reservation it runs under throughTTR_RESOURCES_HOST_RESERVATION. That nested job then runs under the ancestor’s reservation, but only while that reservation is live and its broker or owned child group is a verified process ancestor; otherwise it would deadlock behind its own ancestor. No registered host that could ever hold the job (countingremoteOk) means it fails fast instead of queueing. Each host has separateslots.lightandslots.heavy, per-class CPU/memoryweights, and per-class worker caps (workers). A job is admitted only if its class slot is free and the weighted sum fitscapacity - reserved. -
Reserved headroom.
reserved.cpus,reserved.memoryGiBandreserved.diskGiBare per host, for co-tenants such as production containers, CI runners, Judge workers and the desktop. -
Hosts. The single
localhost uses the lane FIFO for heavy work. Lane owners count as local heavy load, a heavy--hostsjob takes a local lane only while no lane waiter is ahead, and light local work holds a ledger reservation.runnerhosts dispatch the exact pushed commit through the devbox offload transport (runBoxOffload, unchanged). Immediately before remote creation the runner is re-validated against a fresh snapshot that carries the server’s timestamp. If it is stale, draining or missing, its disk is unknown or low, or the snapshot has no server time, the reservation is released and the job requeues without dispatching. Admission also refuses runners in snapshots without a server timestamp, and runners without activity telemetry (other clients’ runs would be invisible). The commit plan is recomputed at dispatch; if it changed, the job falls back to local hosts. SIGHUP is forwarded to the offload transport’s cancellation as SIGTERM, so a closed terminal also stops the remote run. Runs that other clients start on that runner count as heavy load. A runner that refuses creation means no remote run exists, so the job is requeued for local hosts; an ambiguous failure is never repeated locally. -
Admission. Admission requires a fresh trusted runner (
evaluateTrustedRunnerplustelemetryTtlSeconds) and known free disk at or abovemax(reserved.diskGiB, minDiskFreeGiB ?? 50). Unreachable fleets, stale heartbeats, draining runners and unknown disk are never admitted. Dirty or unpushed trees are local-only. -
Canonical reservations. Tickets and reservations live in
<home>/hosts-state/and are created only inside the host-wide admission mutex. Concurrent requesters on this machine therefore cannot double-assign a slot. Each reservation is tied to its broker PID (or owned child group). Cancellation (SIGINT/SIGTERM/SIGHUP) releases it, and a dead owner is pruned on the next round. A malformed entry closes admission. Onlyscope: "host"reservations claim capacity: a project-only lock never stands in for host-wide admission. -
Concurrent slot release. Status and queue observations may overlap a release. A slot removed between its directory and owner reads is absent, including
ENOENT/ENOTDIR, without retrying the observation. Recent ownerless directories still count as initializing. Malformed owners, invalid slot types, unreadable state and missing persistent budget fences keep admission closed.resources-slots-observation.test.tsinjects a release between reads and checks these fail-closed controls. -
Preference. An optional per-host
preferencerank inhosts.json(lower first) orders hosts that already passed every eligibility and capacity check; the capacity score breaks ties, and unranked hosts follow ranked ones. It is an ordering hint only: a preferred host that is stale, draining, degraded, low on or unknown disk, missing telemetry, or full is skipped, never forced, and preference never lets a dirty tree run remotely. Runners are degraded when their 1-minute load per CPU (agent telemetryresources.loadAverage/resources.cpu.cores) reachesmaxLoadPerCpu(default 1); missing load telemetry fails closed, and the pre-dispatch revalidation re-checks it. The operator’s order is Build Mac A > Build Mac B > Linux build host > local hub;ttr resources hosts --simulateprints it with a scenario for today’s health (Build Mac A offline, Build Mac B degraded at load 27 to 32, both skipped) and one with every host healthy. Build Mac A and Build Mac B capacities there are illustrative until an operator registers them (resources-hosts-preference.test.ts). -
Fairness. Waiters are ordered by their project’s live share, then age. A project is the repository’s common Git directory, so all Upskii worktrees share one identity. A waiter overtaken
fairness.maxBypasstimes (default 4) holds every host that could ever fit it until it is placed. This backpressure is what prevents starvation. -
Disk at dequeue (REV-6480-1/2). Every lane-queued job re-checks free disk inside admission, immediately before claim and spawn, using a local
statfsprobe (resources-disk-gate.ts).- It uses
TTR_RESOURCES_MIN_FREE_GIB(default 50 GiB absolute floor). Admission has no default percentage floor (TTR_STORAGE_MIN_FREE_PERCENTdefaults to 0); explicit storage thresholds and inode checks still apply. - Below the floor, or when free disk is unknown, the job keeps waiting (fail closed).
- Block reasons name only the job volume, the free/floor figures and an errno code, never the working path or raw filesystem errors, and status labels them
blockedBy=disk. - The probe runs before taking the admission mutex, only for the FIFO head. It is bounded (2 s) and never stacked on a pending probe, so a hung volume counts as unknown and stalls no other admission.
TTR_RESOURCES_IGNORE_DISK=1overrides it for one command.- Once Lane ST’s
storage-headroomlands, the probe can delegate to it without changing this hook.
- It uses
proposedHostRegistry(), not activated):
Only Linux gains slots. The M4 keeps its heavy ceiling and gains one light slot, because its CPU and disk are already the binding constraints. For the measured queue, the dry run cuts the makespan from 43 to 20 minutes and the maximum wait from 33 to 10 minutes, with every host within its ceiling. Raise Linux heavy slots only after IO PSI
full avg60 stays below 10%.
Before activation:
- register a trusted-development runner on the Linux runner that publishes
capabilities.disk.freeGiB, because unknown runner disk fails closed; - reclaim disk on the M4, or set the floor deliberately: at 4.3% free, the 5% floor holds heavy lane jobs.
resources-hosts-plan.test.ts: ceilings, stale or unknown hosts, fairness and starvation, and project-only locks;resources-hosts-ledger.test.ts: concurrent no-double-assignment, process death, cancellation, overlap and stale-runner dispatch;resources-disk-gate.test.ts;resources-hosts-sim.test.ts.
Capability placement and the M4 hub
Multi-host admission is implemented but requires operator activation: a trusted registry, eligible registered devbox runners, a pushed clean commit and fresh telemetry. SSH reachability alone does not register a runner or admit work. The historical simulation above is an offline example, not live fleet capacity. SethubHostname in hosts.json to the M4’s exact OS hostname. Registries with
runner entries refuse dispatch when that field is absent or differs from the
current machine, before creating a ticket or contacting a runner. Submit fleet
jobs on the M4; remote hosts use their own canonical ttr resources run queue.
The transport remains the keyed devbox API; it does not SSH between hosts or
copy local credentials/environment values.
hubHostname controls dispatch origin only. It does not prohibit local heavy
work or inspect commands for builds. The M4 build prohibition remains an
operator/caller policy; a heavy class also includes permitted focused tests.
Configure local slots/capabilities for the intended placement policy and submit
builds only to remote hosts. Do not treat a matching hub hostname as permission
to build on the M4.
Each host can declare platform: "darwin" | "linux" and a capabilities array
containing ios, macos, flutter, web, rust, docker and/or supabase. Declared
platform must match local OS or fresh runner capabilities.os.platform telemetry;
unknown/mismatched platform blocks placement and is rechecked before dispatch.
Tool capabilities are operator attestations: verify installed tools, daemon
access and runtime acceptance before advertising them. Missing capability tags
cannot satisfy a job’s requirements. Older untagged registries still admit
generic jobs, but cannot admit jobs requiring a capability.
--requires is repeatable and augments direct-tool inference. Xcode tools,
CocoaPods and codesign require macos; Flutter requires flutter and adds ios
for iOS/IPA targets or macos for macOS targets; Cargo/Rust tools require rust;
Docker requires docker; Supabase requires supabase and adds docker for
start, reset and test commands, including through npx and bunx. Known
repository wrappers such as sb:* and mobile scripts infer requirements.
Ambiguous heavy scripts and arbitrary executables must declare their
requirements explicitly for automatic placement. Both Apple tags also require a declared
Darwin host. Configure placement as follows, preserving measured weighted slots,
worker caps and headroom:
Nested --hosts calls validate explicit and inferred requirements against the
actual local host before reusing either a heavy-lane ancestor or a light host
reservation. Incompatible nesting errors before spawning or adding another
ticket; compatible nesting keeps the existing reservation and limits. A nested
call cannot migrate its already-running wrapper to a different host.
Requirement parsing and applicable local compatibility checks also run before
the trusted trivial-command shortcut. Unknown requirements fail closed even
for /usr/bin/true; compatible trivial tools remain admission-free. Explicit
TTR_RESOURCES_BYPASS=1 retains its deliberate raw local-execution behavior.
Set lower
preference on Linux than Studio for portable work; capability checks
still send Apple jobs only to Studio. Do not register held hosts. Studio has
one pool A; the proposed pool B was not created. Do not add a second host entry to count the same machine twice. Mastermind must reconcile all
resource homes and external CI consumers before declaring available slots.
An incompatible starving waiter holds only hosts it could use, leaving Linux
available behind an iOS waiter. Existing repository-share ordering, bounded
bypass and local lane FIFO remain in force. Repeated enqueue preserves the
original ticket owner, requirements, age and bypass count; a reserved token
cannot be enqueued or admitted twice. Requeues retain age/bypass, and the ticket
UUID is the remote create’s idempotency key. An unacknowledged write never
triggers another placement or a local retry; reconcile the original operation
through ttr box list before deciding what happened.
Regression coverage: resources-hosts-nesting.test.ts, resources-hosts-requirements.test.ts,
resources-hosts-dispatch.test.ts and resources-hosts-ledger.test.ts cover
capability/OS placement, hub refusal before writes, ordering, duplicate tickets,
stable operation identity and unknown-outcome refusal.
Studio operator lane update
An operator applied the 2026-10-25 lane change: Studio now has one pool A at 6 base/8 max lanes, and Linux has 3 lanes. Pool B was not created. The physical Studio ceiling remains 16. No further lane raise is proposed by this change; configuration and activation remain operator-owned. The host’s private runbook holds the operational detail; re-read current status before using declared worker totals as capacity evidence. Future changes require a full idle drain (zero owners, waiters, reservations and queued remote runs), saved config/registry/launcher bytes, the same roots and shell settings, a compatible pinned CLI and a reviewedsetup --dry-run.
Let existing work finish in order; never kill/reorder waiters or create another
queue. Apply only as the operator, monitor CPU/memory/disk/I/O and effective
worker caps, and roll back saved settings only after another idle drain.
Operator-owned remaining evidence: reviewed registry installation, actual
runner registration and runtime capability acceptance. Source tests and offline
simulation do not activate multi-host admission or prove remote execution.
Docker metadata lifecycle diagnostic
The SDK metadata subprocess is silent by default. Disposable CI can opt in withCI=true and TTR_DOCKER_METADATA_DIAGNOSTIC=1. Each invocation emits one JSON
record (schema: ttr-docker-metadata-v1) at its existing settlement boundary.
It records a process-local ordinal, Node/Bun runtime versions, monotonic elapsed
milliseconds, spawn/first stdout/error/exit/close times, first and total stdout
byte counts, deadline time and callback lateness, kill return value, fixed spawn
classification, numeric exit code and an allowlisted signal. Numeric counters
saturate at 2,147,483,647; versions are validated and bounded; records stay below
1024 bytes. No arguments, paths, environment values, Docker host/context, metadata,
output or exception text are included.
A null event time means it was not observed before settlement. In particular,
settlement still follows error or exit and never waits for close. The stdout
consumer continues draining with the existing 65,536-character retained cap;
diagnostics add no timers, subprocesses, probes or retries. The 4000ms deadline
and fail-closed capacity behavior remain unchanged. Focused pure mocks in
devbox-docker-metadata-diagnostic.test.ts cover opt-in gating, bounded fields,
event order, deadline/kill outcomes, errors, close timing, overflow and redaction.
Private staff feedback
ttr feedback list|show uses the authenticated, host-scoped Web staff API; it
never resolves a selected workspace or grants access from a local email claim.
The service rechecks current exact-domain activated staff and the canonical
registry for every list/detail operation. The default-off staff feature flag
must be enabled by the release owner before live use.
List returns one bounded metadata page and an explicit filter-bound next cursor.
show <uuid> omits the private body unless --include-content is present.
Status, archive timestamp and revision are read only. With --json, success
goes to stdout; sanitized failures are one JSON object on stderr with
error.code, error.status and a fixed error.message, and the CLI exits 1.
Provider diagnostics, credentials and URLs are never forwarded.
The CLI-to-staff-service composition fixture covers pagination, detail/content
opt-in, status filters and denied current identities/registry reads through the
real PostgREST serializer. These synthetic boundaries do not qualify a hosted
Auth or database deployment.
Node config-file options (--experimental-config-file and --experimental-default-config-file, including attached values) can activate the native test runner. Resource admission treats them as ambiguous and injects --test-concurrency before program arguments. A positive explicit lower concurrency remains lower; zero (Node auto) is bounded by the configured worker cap. Regression: resources-node-config.test.ts includes real Node config-selected five-file runs.
Budget scheduler rollout and rollback
The budget-capable setup preflight runs before--force: force applies only to managed launchers already carrying protocol 3. A foreign legacy launcher is refused even with --force --dry-run. For that incident, an operator must stop all work, take exclusive custody, verify zero owners and waiters, and preserve the config and launcher bytes. Move only the identified foreign managed launcher files to an operator-owned backup outside bin; preserve unmanaged files and any existing budget marker. Then use the verified owning global CLI to run setup with the saved roots and shell settings. Verify every launcher and global version before resuming. Do not use a stale workspace CLI for recovery.
Protocol 3 permanently fences legacy clients on the first normal admission, even when no project budget is configured. The frozen 0.28.0 error Unknown resource slot; admission is closed. means upgrade the global CLI and refresh its shims; it is not permission to delete a live marker. At 12 lanes, 0.28.0 already fails closed independently of this marker.
Before rollout, fix PID start identity #6561 before enabling budgets on long-uptime Studio hosts: PID reuse can retain a dead reservation and stall every project. Source review and a package build do not publish or activate the new scheduler.
- Pause admissions across every project and runner sharing the home. Drain all owners and queued tickets. Save the config, roots, shell settings, worker/lane limits and the exact legacy supplier version for rollback. Take exclusive operator custody before changing installations.
- Publish the qualified budget-capable release through the maintained SDK workflows. Upgrade every owning global install with
~/.bun/bin/bun add -g tuturuuu@<published-budget-version>. Inventory interactive shells, runner services, PATH aliases, caches and other hosts; do not admit new work yet. Remove only confirmed stale workspace aliases using the incident procedure. - Use each upgraded owning global CLI to refresh launchers with the existing roots and shell settings:
~/.bun/bin/ttr resources setup --root <existing-root-1> --root <existing-root-2> --shell <existing-shell>. Setup does not publish the fence. It requires an idle queue when refreshing legacy launchers and refuses a foreign installation; a verified same-global release upgrade is permitted. Do not install this release into a separate npm-prefix path for setup. - Verify every global version and launcher target. New launchers carry
# Resource scheduler protocol: 3. The preflight checks known launcher files in this local resource home only. It does not inventory global installations, PATH aliases, cached binaries, running processes or other hosts; operator inventory is required even if the preflight passes. - Configure budgets atomically while idle, then resume admissions. The first new-version admission fsyncs its staging file, atomically links
slots/budget-v3, and prints upgrade/rollback guidance once. Later admissions reuse the marker and sweep stale staging files. Removing the budget config does not remove this persistent fence.
- Stop every runner pickup, shell and explicit
runthat could start new work. With the new CLI, runresources disable, then verify zero owners, occupied slots and waiting tickets. Disabling alone is insufficient: it would let shims run unmanaged, so all work must remain stopped. - While the fence still exists, use the new CLI’s
resources uninstallto remove managed launchers and shell hooks, including.zshenvactivation. Verify the shim files and managed shell blocks are gone, preserving unrelated shell content. Complete all new-version mutations now; even disable/uninstall can enter the admission lock and publish the fence. - Restore the saved config with admission disabled, no budget and at most two base/burst lanes, preserving roots. Downgrade every global install with
~/.bun/bin/bun add -g tuturuuu@<saved-legacy-version>while work remains stopped. Verify the selected legacy binary version without invoking new-version resource commands. - Inspect high-numbered slot directories and remove only empty ones with
rmdir. Never delete owners or tickets to manufacture idleness. Remove only<resource-home>/slots/budget-v3withrm -- <resource-home>/slots/budget-v3; do not delete the slots tree. - Use the selected legacy owning CLI’s
resources setupwith the saved roots, shell and worker/lane settings. Verify a controlled legacy admission and a fresh noninteractive shell invocation succeed without recreating the marker, then resume work. No new-version admission or mutation may occur after marker removal: it would republish the fence.
resources-budget-publish.test.ts proves fsync-before-link, one-time warning, fast reuse and stale-stage cleanup. resources-budget-upgrade.test.ts kills a publisher with an empty stage and verifies recovery. resources-budget-rollout.test.ts covers legacy preflight, installation-only setup, same-global versus consumer identity and idle upgrade. Native worker charging/caps retain positive lower --test-concurrency, while zero/auto uses the configured cap (resources-node-budget.test.ts).
Cross-process resource fixtures prepare shims once per home before concurrent run launches. Their barriers name the relevant child, operation or detached group, allow a one-second settle window for final writes, and report scoped stderr before the Vitest deadline. Fixed synthetic CPU/memory/load telemetry keeps policy fixtures independent of host contention. The existing 45-second cross-process test budget also covers standalone config cases; idle-warning status composition uses one injected CPU table and asserts warning data, rendered text and the absence of a warning for a busy table. Waiting for a preliminary zero sample cannot stabilise a later ps read, so status must not resample an injected table. A fixture home’s CPU/memory budget is immutable after its first shim is written; changed budgets use a fresh home.
Repository operational scripts, including bun ttr and check:changed, invoke ~/.bun/bin/ttr directly so package-manager PATH changes cannot select stale workspace SDK dist. resources run and shim never install launchers. Setup refuses launchers from another CLI installation. For managed protocol-3 launchers, preview an intentional switch with setup --force --dry-run, drain all jobs, and explicitly apply setup --force; foreign legacy launchers require the operator-controlled recovery above. Same-install Bun global version upgrades remain supported. Source CLI experiments require a disposable resources home. Regression coverage: scripts/trusted-ttr.test.js and resources-provisioning.test.ts.
Launcher mismatch incident recovery
Symptom: the global CLI rejects missing, stale or malformed launchers. A stale workspace SDK dist can write its own entry into shared launchers before admission, and its nested run can block the queue. Never run operational commands through workspace or ancestornode_modules/.bin/ttr or stale packages/sdk/dist.
Repository admission uses the verified global ~/.bun/bin/ttr (stable >= 0.28.0). check:changed and node scripts/ttr-run.js -- COMMAND run directly when the inherited TTR_RESOURCES_ADMITTED or TTR_RESOURCE_OWNER marker is present. When the global install is absent, check:changed warns and runs sequentially with its existing worker limits. The operational wrappers still require a global CLI, and an invalid/old installed global fails with bun add -g tuturuuu recovery guidance. None falls back to a workspace binary. Refresh declared workspace bin links after adopting the source wrapper. bun install alone can leave orphan aliases unchanged; use the explicit orphan inspection/removal procedure below. This does not rebuild dist. The shipped bin works in installed packages; workspace aliases refuse to execute dist, including from bunx.
An operator with installation authority must identify and drain the offending jobs, preserve the existing resource configuration, then restore canonical launchers through the trusted global CLI with the same existing roots and shell settings:
setup --force --dry-run before an intentional cross-install restore, then use setup --force with the same roots after the queue drains. Foreign legacy launchers require the operator-controlled recovery in the budget rollout section; force does not bypass that preflight. The plan reports each old/new launcher. Force never overwrites unmanaged files. Version changes within one Bun global installation are allowed; versioned cache paths normalise to its stable global link. Run/shim never write launchers. Review status for enabled installation and an empty queue before resuming work. Source changes do not repair already-installed links or prove historical writer attribution.
Regression coverage: scripts/trusted-ttr.test.js, scripts/workspace-ttr-bin.test.js, and resources-provisioning.test.ts exercise ancestor lookup, inherited admission, minimum version, stale bins, same-install upgrades, refusal and forced replacement with disposable homes.
Orphan workspace aliases
A refusal can also mean stale workspace bin links in an ancestor checkout that still has a legitimate SDK dependency. Inspect that ancestor’s package.json and links first. For a declared dependency, run~/.bun/bin/ttr resources run -- bun install --force there to regenerate aliases pointing to bin/ttr.cjs; retain its legitimate package link. --no-ttr does not bypass unsafe ancestor links. The removal procedure below applies only to confirmed undeclared orphan symlinks.
If the root manifest has no tuturuuu dependency, old symlinks can remain: node_modules/tuturuuu -> ../packages/sdk and node_modules/.bin/{ttr,tuturuuu,tutur3u} -> ../tuturuuu/dist/cli/index.js. Bun may report no changes and keep them. The trusted repo wrapper and check:changed refuse such ancestor aliases, even inside an admitted lane; they never execute or remove them silently. A healthy global CLI does not make those bare aliases safe.
From the affected ancestor checkout, inspect the manifest and every link first. Only after confirming these are the orphan symlinks above, remove those links explicitly and reinstall under admission:
packages/sdk or its dist. A declared dependency can recreate aliases pointing to the new bin/ttr.cjs wrapper; an undeclared orphan can remain absent. Inspect the resulting links before resuming. Native temp-directory and symlinked-home tests use physical paths, so macOS /var/folders aliases do not change installation identity. Resolve HOME and the Bun install base independently: a symlinked .bun or .bun/install is supported, while entries outside that resolved install base are still refused.
Automatic placement and admission disk floor
ttr resources run --host auto --dry-run -- COMMAND explains one invocation
without starting it or creating a queue ticket. Remove --dry-run to execute.
This reuses the trusted hosts.json, hub fence, devbox telemetry, reservations
and exact-commit transport described above. It creates no daemon or poller.
A missing registry fails closed; the CLI never activates the operator-owned
registry. SSH access alone is insufficient: each remote host needs an eligible
trusted devbox runner and fresh activity, load, OS and disk telemetry.
Capabilities narrow the eligible set before ranking. Declare --requires ios,
--requires flutter, --requires docker or --requires supabase for wrappers;
repeat the flag for multiple requirements. Direct supabase start, reset
and test commands require both the Supabase toolchain and Docker. Apple jobs
need a declared Darwin host. An unreachable fleet/runner, failed probe, stale
heartbeat, unknown OS/load/disk, or capability mismatch excludes that host and
appears in the dry-run explanation. Automatic remote hosts also require sshHost (for example "build-mac-a")
for a bounded read-only admission probe. It reads the canonical ttr queue plus
OS/load/disk headroom; direct SSH jobs are therefore counted even when they
are absent from devbox activity. Probes use ssh with the configured alias,
BatchMode and bounded timeouts, preserving the operator’s SSH configuration
(including any ProxyCommand and host-key checks). They interpolate no
workload command, environment or path into shell text. Probe failure excludes
the host for the invocation. Remote execution still uses the devbox API.
The free-lane order in this example is Build Mac A (preference: 1), Build Mac B (2), Linux (3), then
the local hub (4). A full Build Mac A never causes queueing while an eligible Build Mac B or
another host has a free lane. Linux is considered only after the Studios
cannot take the job, and its configured reserved CPU/memory/slots must preserve
headroom for the host’s other workloads. Set Build Mac B’s slot ceiling to its admitted 2–4 lanes
and keep its checkout footprint small. Free-lane counts use the registered slot/weight budget, live SSH-probed local
queue occupancy and reported devbox running and queued jobs.
Only when every eligible host is full does the invocation wait in the existing
host ledger, reporting the host with the shortest estimated wait. The estimate
uses jobs ahead, usable slots and expectedJobSeconds (default 60 seconds per
job); it is an estimate, not measured remaining runtime. Each admission round
rechecks all hosts and takes a free eligible lane before waiting again. No
eligible host means exit 75. Remote eligibility changes are retried at most
three times before exit 75; ambiguous remote writes retain the existing
operation identity and never fall back blindly.
Remote lane observations have no operation identity that confirms a local
reservation is included. Reservations therefore remain charged until completion,
including the interval before remote creation. Dispatch rechecks other in-flight
reservations using the same class, physical-lane and resource-budget rules as
planning, excluding only its own reservation. Running work can be counted
conservatively twice until identity-bearing confirmation is available. Both light
and heavy remote jobs require a free physical lane; a full remote host can fall
back to eligible local light capacity.
Known repository wrappers infer their toolchains: sb:* scripts require Supabase
and Docker, and mobile/Flutter scripts plus Dart/Melos wrappers require Flutter.
Apple build scripts also require the matching Apple capability. Ambiguous known
heavy run scripts fail automatic placement unless --requires is explicit.
An explicit requirement augments inferred requirements and cannot remove them.
The local admission floor is 50 GiB by default, configurable with
TTR_RESOURCES_MIN_FREE_GIB. Resource admission uses an absolute byte floor by default, so a large volume
with hundreds of GiB free is not refused solely by a default percentage. Explicit
TTR_STORAGE_MIN_FREE_PERCENT and the existing storage byte/inode floors still
apply. Storage health reporting retains its own percentage defaults. ttr resources status [--json] reports the configured floor,
current measured headroom and admission verdict. Invalid admission configuration
is reported in structured status and preserved as the remote probe exclusion
reason. Falling below the admission floor enters the storage reclaim and blocker
reporting path; successful reclaim must reach that floor before a job is admitted.
A remote host’s effective
floor is max(reserved.diskGiB, minDiskFreeGiB ?? 50) in hosts.json and is
rechecked before dispatch. Disk probes that fail are unavailable, never zero
cost capacity. Disk overrides remain explicit operator decisions.
Commands stay argv through the commit-run envelope and shell: false; no shell
requotes them. The existing local queue still enforces worker caps. Ambient
local environment is not copied to remote hosts. For exact explicit nonsecret
environment assignments use argv, for example:
hubHostname; preview the command; then enable only
the reviewed hosts. This implementation does not perform that step. Tests in
resources-hosts-auto.test.ts cover free-lane fallback, all-busy wait selection,
Linux priority, unreachable/capability/OS/unknown-telemetry exclusion, disk
thresholds, dry-run cleanup, and literal command/environment round trips.
Storage recovery on large volumes
Default percentage recovery is the smaller of twice the low percentage and the low percentage’s byte watermark plus 100 GiB on that volume. An 8 TB-class volume with a 5% low threshold can therefore recover at 537 GiB free without reaching 10%. Admission also keeps its absolute 50 GiB floor’s recovery band: by default the larger of twice the byte low threshold and the floor plus 10 GiB. An explicitTTR_STORAGE_RECOVER_FREE or
TTR_STORAGE_RECOVER_FREE_PERCENT can lower the corresponding recovery
threshold. Recovery remains at least 1 GiB or 0.1 percentage point above low;
an explicit byte setting can narrow the admission floor band to 1 GiB. Other
byte, percentage and inode checks still apply. Host overrides remain operator
owned; remove temporary recovery workarounds only after adopting the released
CLI and verifying status. storage-hysteresis-recovery.test.ts and
resources-disk-floor.test.ts cover recovery and override boundaries.