Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
793 lines
49 KiB
Markdown
793 lines
49 KiB
Markdown
# Agent Extensions
|
||
|
||
Status: current implemented architecture
|
||
|
||
Agent Extensions let independently released ACP agents integrate with Tutti
|
||
without adding provider-specific executable code to this repository. An
|
||
extension is declarative data: a manifest,
|
||
discovery/tool/capability/composer/authentication/account-usage profiles, locale
|
||
resources, and static assets.
|
||
|
||
## Trust And Distribution
|
||
|
||
Configured sources live in `config/tutti.defaults.json`. Each source pins an
|
||
agent key, exact Extension version, HTTPS `versions.json` URL, default
|
||
activation state, signing key ID, and Ed25519 public key. `tuttid` accepts only
|
||
that client-pinned release when it remains active and compatible and its
|
||
canonical release JSON signature, artifact SHA-256, byte size, manifest
|
||
identity, and package contents all validate. Publishing a newer release to the
|
||
remote index does not change an already shipped Tutti client; adopting it
|
||
requires a Tutti release that updates the source's `pinnedVersion`.
|
||
|
||
During a versioned metadata-path rollout, a source may declare ordered fallback
|
||
index URLs. Fallback is permitted only when a higher-priority index cannot be
|
||
fetched, such as before the new CloudFront path has been published. Once an
|
||
index is fetched, its JSON shape, agent identity, compatibility, active or
|
||
withdrawn state, and selected release signature are authoritative and fail
|
||
closed; a legacy index must never revive a release rejected by that authority.
|
||
Remove rollout fallbacks after the new metadata path is published and the
|
||
supporting Tutti version has propagated.
|
||
|
||
`minTuttiVersion` is evaluated only after the complete versions document has
|
||
been decoded. It therefore cannot protect an older strict decoder from a new
|
||
field inside an embedded manifest. A release that extends the manifest wire
|
||
shape must publish `versions.json` under a new versioned metadata path, leave
|
||
the previous index unchanged for older Tutti builds, and switch the configured
|
||
source URL only in a Tutti release that understands the new shape. Immutable
|
||
artifacts may remain under the Agent's existing release path because older
|
||
clients cannot select them through their frozen metadata index and exact
|
||
version pin.
|
||
|
||
Release ZIPs are data-only. Installation rejects path traversal, symlinks,
|
||
executable regular files, unsupported file types, excessive entry counts, and
|
||
excessive compressed or expanded sizes. Directory entries may carry the normal
|
||
execute/search bits required to traverse them. The package may describe an exact
|
||
standard npm, pnpm, or uv runtime installation, or pin raw official executable
|
||
artifacts, but it never carries executable code itself. Binary artifact
|
||
metadata remains inside the v2 manifest covered by the extension release's
|
||
Ed25519 signature and signed package digest; there is no manifest v3 or
|
||
reinterpretation of existing package-manager manifests.
|
||
|
||
Each concrete Agent repository owns its reproducible archive, release signing,
|
||
versions generation, verification, and S3/CloudFront workflow. Tutti consumes
|
||
signed immutable releases but does not build or upload third-party Agent
|
||
artifacts. The provider-independent setup and release procedure lives in the
|
||
`tutti-os/tutti-agent-extension-skill` repository.
|
||
|
||
Local development has one explicit exception modeled after the App Center
|
||
catalog override. In `development` only,
|
||
`TUTTI_AGENT_EXTENSION_<KEY>_PACKAGE_DIR` may select an unpacked package for a
|
||
configured source. The daemon applies the same data-only file, size, manifest,
|
||
profile, asset, and runtime-contract validation, copies the package into its
|
||
owned state, stamps a content-addressed `+local.<digest>` snapshot version, and
|
||
registers the normal fixed Agent Target. It never runs from the mutable source
|
||
directory. Every daemon start with an explicit local override synchronously
|
||
snapshots and validates the currently configured directory before serving the
|
||
Agent Target. A cached `+local` snapshot is never an offline fallback for a
|
||
missing or invalid development directory; reconciliation removes the stale
|
||
Target while preserving its enabled preference when a valid new snapshot is
|
||
registered. Removing the override requires the signed remote source again.
|
||
Production ignores this override and continues to require the signed HTTPS
|
||
release path.
|
||
|
||
Development clients may use synthetic or zero application versions while the
|
||
desktop is running from source. Remote extension selection still requires the
|
||
configured active pin, signed release, and declared host capabilities; the
|
||
release's `minTuttiVersion` gate is enforced only for packaged production
|
||
clients, whose version is a release compatibility claim.
|
||
|
||
## Installation And Runtime Ownership
|
||
|
||
Verified installations are immutable and stored under:
|
||
|
||
```text
|
||
<state>/agent/extensions/<agentKey>/<version>/
|
||
<state>/agent/extensions/<agentKey>/active.json
|
||
```
|
||
|
||
Development package snapshots use the same layout and immutable installation
|
||
contract. Their synthetic version separates local bytes from a published
|
||
version with the same source manifest version; changing any package file
|
||
creates a new fixed snapshot on the next daemon start.
|
||
|
||
Installation persistence follows daemon layering. `biz/agentextension` owns
|
||
the installation record contract; `service/agentextension` owns release
|
||
verification, package promotion, activation, and reconciliation; the narrow
|
||
`data/agentextension` installation adapter alone derives `agent/extensions`
|
||
paths and reads or atomically replaces `installation.json` and `active.json`.
|
||
Service code must not reconstruct the daemon state root. Managed-first Runtime
|
||
selection is derived from the running client's exact source pin and is not an
|
||
independent durable installation preference. Readers accept and discard the
|
||
legacy `preferManagedRuntime` field so strict decoding remains compatible with
|
||
records written during the initial rollout.
|
||
|
||
The active record registers a system Agent Target with an
|
||
`agent_extension` launch reference fixed to `<agentKey>@<version>`. The
|
||
Target carries the package icon, optional mask icon, and optional home hero
|
||
image as data URLs, so renderer code does not add presentation assets or
|
||
provider branches for every extension. Package `icon` is the colored identity
|
||
reused by the provider rail, conversation identity, Message Center, and
|
||
mentions; optional package `maskIcon` is the mask-safe glyph for conversation
|
||
rows. All assets originate in the verified package and remain pinned to the
|
||
active installation version.
|
||
|
||
At launch the runtime controller asks `AgentRuntimeResolver` for unknown
|
||
providers. The resolver verifies the fixed installation reference, evaluates
|
||
the declarative discovery profile, and creates the generic standard ACP
|
||
adapter. Client-pinned remote installations prefer their matching
|
||
Tutti-managed runtime, with a compatible local runtime as a temporary fallback
|
||
while automatic convergence is pending. Local development snapshots retain
|
||
local-first discovery. A candidate may add signed
|
||
`searchPaths` entries with `scope: "user"`; every path must be a bounded
|
||
relative path below the current user's home directory. Those paths are
|
||
prepended to the shared runtime-command environment, so the extension can
|
||
describe an official vendor install location without adding provider-specific
|
||
filesystem code to `tuttid`. It never loads JavaScript, React, Go plugins, or
|
||
native modules from the extension.
|
||
|
||
Composer skill roots remain declarative runtime inputs. Workspace roots must be
|
||
safe relative paths at both installation validation and service consumption
|
||
boundaries; absolute and parent-traversing paths are ignored before runtime
|
||
preparation or discovery. Tutti materializes only its managed skills into those
|
||
roots and replaces the same managed directories on repeated preparation, so a
|
||
persistent workspace cannot accumulate suffixed copies. User-owned colliding
|
||
directories are never replaced.
|
||
|
||
The generic adapter applies declarative tool aliases before canonical activity
|
||
normalization and maps composer permission semantics onto runtime permission
|
||
IDs. Standard ACP content diffs continue through the shared ACP diff
|
||
normalizer, so Gemini and future extensions do not add provider branches to
|
||
AgentGUI. Both standard ACP `models` state and legacy `configOptions` are
|
||
normalized into the shared composer model descriptor; the catalog remains
|
||
runtime-reported instead of being hardcoded for an extension provider. A
|
||
prompt-free composer discovery session runs in the normalized selected project
|
||
scope. When no project is selected, it uses the daemon-owned discovery directory
|
||
under `<state>/agent/discovery/<provider>`, because standard ACP session creation
|
||
requires a concrete working directory.
|
||
|
||
### Provider-owned account usage
|
||
|
||
An Extension may declare the optional `accountUsage` profile with schema
|
||
`tutti.agent.account-usage-probe.v1`. The profile pins one exact npm or pnpm
|
||
companion package, `node-script` entry below `${installRoot}`, fixed argv, and a
|
||
bounded timeout. The Extension ZIP remains data-only: the companion is a
|
||
separately published Provider-owned artifact with its own runtime identity,
|
||
install root, activation, and verification lifecycle. It is never part of the
|
||
primary ACP install command, runtime identity, activation, resolution, or
|
||
adoption. A companion download, install, verification, or activation failure
|
||
therefore leaves the Agent ready and only makes the account-usage endpoint
|
||
return `runtime_unavailable`; a status read never downloads code. The
|
||
independent reconciler persists a diagnostic-light failure record and its next
|
||
bounded retry time, so daemon restart preserves backoff. Recovery deletes the
|
||
record.
|
||
|
||
`runtime.install` may optionally give the companion its own installer when the
|
||
Agent runtime itself is not managed by npm or pnpm. The installer is a closed
|
||
declaration: `runner` is exactly `npm` or `pnpm`; `args` contains 1–8 bounded,
|
||
shell-free argv elements, names the exact scoped package declared by
|
||
`runtime.package`, and may use only the complete `${installRoot}` and
|
||
`${platform}` placeholders. Without this declaration, the companion inherits
|
||
the Agent runtime installer and that installer must already be npm-compatible.
|
||
The independent installer changes only companion preparation; it does not
|
||
change the Agent runtime's install, activation, command, or provider identity.
|
||
|
||
Before every probe, tuttid verifies the fixed host Node interpreter and the
|
||
ordinary in-root CommonJS script independently. The script is supplied to Node
|
||
as the already verified bytes instead of executing an npm `.cmd`/shell shim or
|
||
reopening the mutable script pathname. This is the same contract on Windows,
|
||
macOS, and Linux. Fingerprinting and snapshot construction observe the probe
|
||
context. Windows keeps one content-addressed, verified `node.exe` snapshot in
|
||
daemon-private state and holds it against writes while probes reuse it; after a
|
||
daemon restart the snapshot is verified once instead of copying the source
|
||
interpreter again.
|
||
|
||
Local Extension development may replace that managed companion with one
|
||
explicit executable through
|
||
`TUTTI_AGENT_EXTENSION_<KEY>_ACCOUNT_USAGE_EXECUTABLE`. This is accepted only
|
||
for a development-mode installation with local-package provenance; the path
|
||
must be an absolute, ordinary, non-symlinked, fingerprint-stable JavaScript
|
||
file. The Node interpreter is still resolved and verified separately.
|
||
Production ignores the override and continues to require the exact companion
|
||
package pinned by the signed profile.
|
||
|
||
Every managed probe receives `TUTTI_AGENT_RUNTIME_INSTALL_ROOT` with the
|
||
verified install root of the corresponding Agent runtime. This is a narrow
|
||
Provider-facing bridge for companions that need to delegate usage lookup to
|
||
their Agent package; it is not a general credential or endpoint channel. The
|
||
value follows the host's native path syntax, so companions must consume the
|
||
environment value directly rather than constructing a user-home path or
|
||
assuming POSIX separators.
|
||
|
||
The companion owns all Provider-private behavior, including config and
|
||
credential lookup, OAuth issuer-to-usage-origin binding, endpoint paths,
|
||
refresh timing, and response conversion. It prints only one bounded versioned
|
||
JSON result to stdout; stderr is discarded at the process boundary. `tuttid`
|
||
strictly decodes a closed `available | unsupported | error` result, attaches
|
||
the exact Agent Target and provider identity, and exposes only stable error
|
||
codes and normalized quota fields. Unknown fields, schemas, enums, malformed
|
||
numbers, empty subscription success, and trailing output fail closed. Desktop
|
||
and AgentGUI never select this capability by provider name and never receive
|
||
tokens, configured endpoints, paths, raw response bodies, or Provider error
|
||
text.
|
||
|
||
The normalized account-usage v2 snapshot separates billing identity from quota
|
||
completeness. `billingMode` is `api`, `subscription`, `coding_plan`, or
|
||
`provider_account`; `quotaState` is `complete`, `unavailable`, or
|
||
`not_applicable`. Only `complete` may carry quota rows. `api` is explicitly
|
||
`not_applicable`; a recognized plan or Provider account whose complete balance
|
||
cannot be proven remains `unavailable` without an authoritative empty or
|
||
partial balance. Exact balances use Provider-neutral amount/unit fields such as
|
||
`credits`, while tokens, account IDs, and raw package records remain inside the
|
||
companion. Tutti accepts the original v1 helper result for existing Extensions,
|
||
normalizes it to v2 at the daemon boundary, and requires new companions to emit
|
||
v2 whenever they need these completeness or exact-amount semantics.
|
||
|
||
### Spawn Settings And ACP Workflow Modes
|
||
|
||
Composer profile v1 has an optional, closed `launchSettings.permission`
|
||
declaration for agents whose permission tier is fixed when the process starts.
|
||
It accepts only the `${permissionMode}` placeholder as one complete discovery
|
||
`launchArgs` element, requires exactly one occurrence, and requires exactly one
|
||
unique runtime value for each Tutti semantic: `ask-before-write`, `auto`, and
|
||
`full-access`. The default semantic is fixed to `ask-before-write`. Empty
|
||
values, unknown or combined placeholders, unknown semantics, duplicate or
|
||
ambiguous values, and shell syntax fail installation validation. Expansion is
|
||
an argv-element replacement; it never invokes a shell or weakens the existing
|
||
runtime command validation.
|
||
|
||
Spawn-time permission is not an ACP workflow mode. The selected permission is
|
||
captured by conversation creation and changing it requires a new session. The
|
||
adapter does not send `session/set_mode` for that permission. An extension may
|
||
separately declare `workflowModes.plan.enabledRuntimeId` and
|
||
`disabledRuntimeId`; the shared Plan switch and `/plan` then send those two
|
||
validated values through standard `session/set_mode`. An agent whose Plan mode
|
||
exists only as a launch-time permission may additionally declare
|
||
`updateStrategy: "restart-with-launch-permission"`. For that strategy the
|
||
adapter replaces only the ACP process, loads the same provider session, uses the
|
||
enabled runtime ID while Plan is active, and restores the conversation's fixed
|
||
permission runtime value when Plan is disabled. A failed restart leaves the
|
||
previous live process and canonical settings unchanged. Profiles without these
|
||
optional declarations retain their existing ACP session-mode/config-option
|
||
behavior.
|
||
|
||
Composer profile v1 may also opt in to
|
||
`setModel.reasoningEffortMeta`. For that profile, the standard ACP
|
||
`SessionModelState.models.availableModels` entries are the only model and
|
||
reasoning catalog. Tutti preserves `supportsReasoningEffort`,
|
||
`reasoningEffort`, `reasoningEfforts` (including runtime labels and
|
||
descriptions), and `supportsImageInput`, whether supplied as supported model
|
||
fields or in model `_meta`. The daemon projects those facts through typed
|
||
composer options and `reasoningOptionsByModel`; AgentGUI therefore changes the
|
||
reasoning selector when the selected model changes. Unsupported models carry
|
||
an authoritative empty profile. A model or reasoning update uses standard
|
||
`session/set_model`; the validated current effort is added as
|
||
`_meta.reasoningEffort` only for a model that advertises reasoning support.
|
||
The established standard ACP config-option reasoning path remains unchanged
|
||
for profiles that do not opt in.
|
||
|
||
Slash-command narrowing remains declarative. A future extension can mark its
|
||
signed command catalog authoritative and allow only shared commands such as
|
||
`compact`, `status`, and `plan`; runtime-private commands are then removed
|
||
before composer projection. Image blocks, tool calls, permission requests,
|
||
cancel, resume/load, skills roots, and the client-provided standard ACP MCP
|
||
array continue through the generic adapter. The current Tutti session-create
|
||
contract supplies an empty MCP array and does not manage an agent's private MCP
|
||
configuration; adding a non-empty product-level MCP input is a separate Host
|
||
contract change.
|
||
|
||
Standard ACP process recycling is capability-driven rather than extension-name
|
||
driven. When the live `initialize` response advertises `session/load` or
|
||
`session/resume`, the shared 30-minute idle reaper may close only that ACP
|
||
transport and CLI process while retaining the Tutti session and provider
|
||
session id. The next execution launches a new process and restores the provider
|
||
session. The reaper does not send `session/close`, skips providers without a
|
||
proven restore method, and treats pending interactive requests as busy.
|
||
|
||
Extension composer controls stay runtime-owned after the model list is
|
||
discovered. `tuttid` selects the newest context only within the exact workspace,
|
||
normalized project, Agent Target, fixed installation, and request-settings
|
||
scope. It may use an exact live or pinned persisted context, or a single-flight
|
||
hidden discovery result, to project only the model, permission, and reasoning
|
||
fields identified by the signed composer `configOptions.acpOptionId` references,
|
||
plus `availableCommands` into the slash-command catalog. The same signed option
|
||
IDs drive standard ACP startup and live setting writes; legacy top-level model
|
||
and permission source declarations map to `model`, `mode`, and the established
|
||
`reasoning_effort` alias. Persisted
|
||
runtime context is an internal recovery input only: the public composer response
|
||
publishes commands and per-model reasoning profiles through typed
|
||
`commands` and `reasoningOptionsByModel` fields. Those typed fields are
|
||
authoritative for desktop and AgentGUI projections; `runtimeContext` remains
|
||
opaque legacy/diagnostic data and is not an expansion seam for composer
|
||
capabilities. Legacy persisted contexts without the fixed installation and
|
||
profile identity are not eligible for reuse. Hidden extension discovery is
|
||
prompt-free and is closed immediately after success, start/terminal failure,
|
||
cancellation, or timeout. When discovery completes after the caller's bounded
|
||
wait has returned, tuttid publishes the existing model-catalog refresh signal;
|
||
active composer consumers then reload the newly cached catalog instead of
|
||
requiring a new conversation. The selected model is not part of the discovery
|
||
cache identity because the hidden discovery session does not receive a model;
|
||
acknowledging its advertised default must continue to address the same catalog.
|
||
The standard ACP adapter canonicalizes
|
||
provider-native reasoning option ids such as `thought_level` or `effort` to
|
||
Tutti's `reasoning_effort` before they reach service or GUI projections, while
|
||
retaining the original runtime id for ACP writes. Unknown provider-native
|
||
options remain intact in the opaque runtime context; this does not imply a
|
||
generic AgentGUI control for every unknown option.
|
||
|
||
Model consumption metadata follows the same adapter-first rule without making
|
||
provider prose globally meaningful. A verified Extension may opt its model
|
||
config reference into the closed
|
||
`descriptionMetadataFormat: "credit-consumption-multiplier-v1"` declaration.
|
||
Only then does the standard ACP adapter remove a standalone `xN credits`
|
||
description segment and project its numeric token as the typed
|
||
`consumptionMultiplier` model field. Unknown formats fail package validation
|
||
and adapter construction; profiles without the declaration preserve the full
|
||
description. The daemon API and activity adapter preserve the typed field, and
|
||
AgentGUI renders it without parsing provider prose or branching on a provider
|
||
name.
|
||
|
||
Signed composer profiles may narrow the provider-advertised slash-command
|
||
catalog and attach shared command effects such as submit-immediate, show-status,
|
||
activate-goal-mode, and toggle-plan-mode. `tuttid` applies that declarative
|
||
policy before returning composer options, so extension commands can reuse the
|
||
shared AgentGUI slash-command behavior without a provider-name branch. Signed
|
||
profiles may also set
|
||
`skills.runtimeCommandProjection: "unlisted-as-skills"` alongside an
|
||
authoritative slash-command catalog. In that mode, runtime-advertised entries
|
||
outside the signed core-command list are projected through the typed `skills`
|
||
field with their exact slash trigger and runtime description. AgentGUI then
|
||
renders separate command, capability, and skill groups without a
|
||
provider-name branch; known Tutti-injected routing skills remain hidden from
|
||
the composer picker.
|
||
Signed capability profiles may declare canonical GUI capabilities such as
|
||
`compact` and `planMode`. A declaration becomes effective only when current ACP
|
||
runtime facts and host support also establish it. The closed, signed
|
||
`workflowModes.plan` enabled/disabled ID pair is itself sufficient runtime
|
||
contract evidence for `planMode`, including agents that implement
|
||
`session/set_mode` without advertising a mode catalog from `session/new`.
|
||
For the launch-restart strategy, the same evidence is combined with the closed
|
||
spawn-permission declaration; the Plan runtime value must be distinct from all
|
||
three permission values.
|
||
Duplicates are removed, and unknown
|
||
extension-local capability keys remain package metadata rather than entering
|
||
the Agent Activity capability contract.
|
||
|
||
Extension-owned provider identities remain open metadata after an Agent Target
|
||
authorizes the launch. The shared provider registry validates their canonical
|
||
shape before activity events are created, so turn lifecycle and message events
|
||
retain identities such as `acp:example`; runtime authority still comes only
|
||
from the fixed `agent_extension` Target reference.
|
||
|
||
The standard ACP adapter stamps each turn transition with a sequenced,
|
||
adapter-origin lifecycle snapshot. Reporters and GUI consumers copy that
|
||
provider-independent snapshot, so completed, failed, and canceled extension
|
||
turns clear their active turn reference without requiring the extension
|
||
provider to be added to the built-in event projection catalog.
|
||
|
||
The current runtime adapter registry is still keyed by open provider ID for the
|
||
daemon lifetime. A cached generic adapter now fails closed when the requested
|
||
Target or fixed installation differs, while composer-context reuse uses the
|
||
full scope above. Sessions persist `agentTargetId` and resume re-derives the
|
||
extension installation from that Target. A composite session-pinned
|
||
runtime/profile fingerprint remains required before any future automatic
|
||
in-session migration. The daemon does not activate a newly published remote
|
||
Extension dynamically: source activation is controlled by the Tutti client's
|
||
exact version pins, and existing sessions stay pinned to their recorded Target
|
||
installation.
|
||
|
||
## Target-managed Runtime Setup
|
||
|
||
The daemon exposes Target-scoped setup resources at
|
||
`GET /v1/workspaces/{workspaceID}/agent-targets/{agentTargetID}/setup` and
|
||
`POST /v1/workspaces/{workspaceID}/agent-targets/{agentTargetID}/setup/install`
|
||
or `/setup/authenticate`. Setup never requires a selected project. Install
|
||
submission supplies only the daemon-issued plan digest and a client action ID.
|
||
`tuttid` resolves the workspace, enabled Agent Target, fixed extension
|
||
installation, and verified package manifest before deriving the runner argv,
|
||
exact package identity, install root, executable, launch arguments, and SHA-256
|
||
plan digest. Renderer input cannot replace any execution field.
|
||
|
||
After a client-pinned remote Extension is activated, a daemon-owned background
|
||
reconciler automatically installs the exact Runtime declared by that signed
|
||
Extension into Tutti's private runtime root. It runs once at startup, wakes
|
||
after source refresh or activation, and tracks retry state independently for
|
||
each Target. Transient installation failures use exponential backoff capped at
|
||
30 minutes without rechecking settled Targets. Permanent contract, platform,
|
||
or user-command ownership conflicts wait for a source or preference wake, or
|
||
an explicit setup action, instead of retrying on a timer. It never writes to or replaces a
|
||
user-owned PATH executable, and local development snapshots are excluded. The
|
||
Target-scoped setup endpoints remain the observable repair and authentication
|
||
surface; automatic convergence reuses the same verified install pipeline as an
|
||
explicit setup action.
|
||
|
||
Runtime roots use
|
||
`~/.local/share/tutti/agent-runtimes/<agentKey>/<runtimeIdentity>`, where
|
||
`runtimeIdentity` is derived from the runtime contract: Agent key, runtime
|
||
kind, platform, install runner and argv, exact package identity, launch
|
||
executable and args, and discovery profile. Extension metadata changes such as
|
||
localized copy or presentation assets do not force a reinstall when this
|
||
runtime contract is unchanged. This stable user-local root is shared by
|
||
development and production; daemon state keeps only setup action metadata.
|
||
`${platform}` resolves to Go's `<GOOS>-<GOARCH>` pair. Plan digests still bind
|
||
the Target and fixed extension installation in addition to the runtime
|
||
identity, platform, and complete resolved command, so one managed runtime is
|
||
reused across workspaces while setup actions remain tied to the exact Target
|
||
installation that the user confirmed. Every install action recomputes the plan
|
||
and compares its digest before creating files or processes.
|
||
|
||
The signed release is authoritative for runtime commands. Tutti persists both
|
||
the complete signed release record (preserving its signed JSON shape) and the
|
||
exact signed ZIP beside the extracted package. Every remote-package load
|
||
reverifies the Ed25519 signature against the configured source key, checks the
|
||
ZIP's signed SHA-256 and size, derives a
|
||
canonical content identity directly from those signed ZIP bytes, and compares
|
||
that identity and complete manifest with the active package. The copied manifest
|
||
and digest in `installation.json` are metadata and cannot redefine authority. A
|
||
same-version package directory whose content or authority differs is rejected
|
||
during load and atomically replaced only after a newly downloaded signed
|
||
artifact has been verified. Local development snapshots remain explicitly
|
||
content-addressed and are not interpreted as signed releases.
|
||
|
||
Remote manifest-v2 npm, pnpm, and uv installations created before persisted
|
||
signed-package authority remain readable offline through a closed legacy path.
|
||
That path requires an installation-v1 record with no release digest/size, no
|
||
persisted signed-release files, no binary artifacts, and no user-command
|
||
publication override. Tutti validates the complete package and manifest twice
|
||
around a canonical content fingerprint, then atomically records that fingerprint
|
||
in the legacy installation record. Later loads require the exact migrated
|
||
snapshot. Partial signed-authority state never falls back to legacy validation,
|
||
and legacy packages are redownloaded into signed authority when their source is
|
||
reachable rather than being reinterpreted as a signed installation.
|
||
|
||
Setup probes a compatible executable from signed user-relative search paths and
|
||
the shared daemon runtime PATH. Local development snapshots remain local-first.
|
||
For a client-pinned remote installation, an already verified managed Runtime
|
||
wins; a compatible local Runtime is used only until automatic managed-runtime
|
||
convergence succeeds. The desktop daemon does not source interactive shell
|
||
startup files such as `.zshrc`; vendor install locations outside its process
|
||
PATH belong in extension metadata, not provider-specific core code. Otherwise,
|
||
the installer runs manifest-owned argv directly in a private staging root beside
|
||
the fixed user-local installation; it does not invoke a shell or mutate any
|
||
project package manifest, lockfile, `node_modules`, or global package state.
|
||
Environment inheritance is allowlisted. Runner CWD and package-manager
|
||
cache/config live in a Tutti-managed scratch directory under that same
|
||
user-local runtime root. Tutti rejects symlinks in every existing ancestor of
|
||
the configured managed root. Staging, scratch, replacement, rollback, and final
|
||
activation use no-follow directory handles and relative directory operations;
|
||
the held handles are compared with their path identities before each boundary
|
||
on Unix. Activation metadata is written atomically relative to the held staging
|
||
descriptor, so replacing the staging pathname or placing a link at the metadata
|
||
name cannot redirect a write. Windows retains the established package-manager
|
||
staging and activation path for npm, pnpm, and uv runtimes; raw binary runtimes
|
||
remain fail-closed there until an equivalent no-follow activation implementation
|
||
is available.
|
||
|
||
For package-manager runtimes, `tuttid` resolves a regular executable inside
|
||
staging. A v2 manifest may instead set `runtime.install.runner` to `binary` and
|
||
provide a bounded `artifacts` catalog. Every entry is a raw `executable` pinned
|
||
by exact `<GOOS>-<GOARCH>` platform, semantic version, HTTPS URL, lowercase
|
||
SHA-256, byte size, and an `official-release` HTTPS provenance URL. Unsupported
|
||
platforms have no fallback and fail before download. The selected artifact is
|
||
streamed directly into the private staging root with exclusive creation and a
|
||
hard byte limit; Tutti verifies response transport, size, digest, executable
|
||
mode, and native Mach-O/ELF/PE platform identity. It never invokes an upstream
|
||
installer, shell, archive extractor, or PATH/rc mutation for this runtime kind.
|
||
|
||
The `uv` runner does not follow the stage-then-rename model: uv tool
|
||
environments embed absolute paths (bin symlinks, venv shebangs, `pyvenv.cfg`)
|
||
and cannot survive the activation rename. uv runtimes install in place. Tutti
|
||
first resolves a Tutti-managed uv toolchain — a pinned uv version declared in
|
||
`config/tutti.defaults.json` (`agentRuntimeTools.uv`) with per-platform
|
||
archives, SHA-256, and byte sizes — downloaded with the same streaming
|
||
verification as binary artifacts, extracted as a single pinned member, and
|
||
cached under `~/.local/share/tutti/agent-runtimes/_tools/uv/<platform>/<version>`.
|
||
The user machine needs no preinstalled uv or Python. The install invokes the
|
||
managed uv executable by absolute path, while also prepending its directory to
|
||
`PATH` for uv subprocesses. Confinement variables point into the final root:
|
||
`UV_TOOL_DIR=<installRoot>/tools`, `UV_TOOL_BIN_DIR=<installRoot>/bin`,
|
||
`UV_PYTHON_INSTALL_DIR=<installRoot>/python`, a shared content-addressed
|
||
`UV_CACHE_DIR` under `_tools/uv/cache`, and `UV_NO_CONFIG=1`. Tutti also sets
|
||
`UV_PYTHON=3.12` and `UV_MANAGED_PYTHON=1`, so package resolution and execution
|
||
use a Tutti-owned Python 3.12 instead of whichever system Python happens to
|
||
appear on the daemon PATH. A previously committed root (valid
|
||
`activation.json`) is moved to `<runtimeIdentity>.previous` before installing
|
||
and restored on any failure; an uncommitted partial root is discarded, and a
|
||
missing root with a self-consistent backup (matching activation identity plus
|
||
executable fingerprint) is restored instead of reinstalling. `activation.json`
|
||
remains the commit marker, and the fingerprint/version/ACP-probe verification
|
||
chain is unchanged.
|
||
|
||
For both install kinds, Tutti fingerprints the ordinary in-root executable,
|
||
runs the discovery profile's version check, then performs ACP `initialize` and
|
||
`session/new`. A binary runtime must report the exact artifact version, not
|
||
merely satisfy the local-discovery compatibility range. Binary version probes
|
||
start from the same verified descriptor on Linux or daemon-private immutable
|
||
snapshot on macOS as the final ACP process; the replaceable managed pathname is
|
||
never executed after an earlier fingerprint check. Fingerprints are rechecked
|
||
after ACP process boundaries and again after the atomic staging rename.
|
||
Authentication failures produce `auth_required`; protocol or runtime failures
|
||
fail the action. Successful and auth-required runtimes receive an
|
||
`activation.json` record. Symlinked package-manager bin shims are resolved to an
|
||
ordinary in-root file before activation and launch.
|
||
|
||
Binary replacement uses `<runtimeIdentity>.previous` as its only durable
|
||
intermediate state. Before activation removes any existing backup, and before
|
||
resolution trusts an active root, Tutti resolves an interrupted replacement.
|
||
If the active root matches the current installation identity and exact signed
|
||
artifact, it remains authoritative and the backup may be removed. If the active
|
||
root is absent or invalid, the backup is promoted only after its activation
|
||
identity, executable path, SHA-256, size, executable mode, and native platform
|
||
all match the current signed artifact; the held directory is checked again
|
||
after the rename. An invalid backup is never adopted and, when no verified
|
||
active root exists, is preserved for diagnosis. Thus interruption after either
|
||
replacement rename converges on the verified old or verified new runtime
|
||
without silently discarding the active runtime.
|
||
|
||
By default, successful activation also publishes the manifest launch
|
||
executable's basename at `~/.local/bin/<agent-command>`. The user entry points
|
||
to `~/.local/share/tutti/agent-runtimes/<agentKey>/bin/<agent-command>`; that
|
||
stable per-Agent entry points to the executable in the fixed runtime root and
|
||
is atomically repointed on upgrade. Unix uses Tutti-owned symlinks for both
|
||
hops. Windows uses Tutti-owned `.cmd` launchers because ordinary desktop users
|
||
do not have file-symlink privilege; the launcher preserves arguments and the
|
||
child exit code. A pre-existing regular file, foreign symlink, or foreign
|
||
Windows launcher at either entry is never overwritten. Feature disablement and
|
||
daemon shutdown do not remove a published command, so it remains usable outside
|
||
Tutti while the managed runtime files remain installed.
|
||
|
||
The optional v2 `runtime.launch.publishUserCommand` field may be `false` for a
|
||
runtime that should be private to AgentGUI. Absence preserves publication for
|
||
every existing manifest. An opted-out runtime launches the verified fixed
|
||
executable inside `installRoot` and neither creates nor verifies the two
|
||
publication entries. A pre-existing regular file, foreign symlink, or foreign
|
||
Windows launcher at the user command path is therefore ignored and cannot
|
||
prevent managed activation; Tutti still never overwrites, removes, or repoints
|
||
that entry.
|
||
|
||
### Declarative Launch Environment
|
||
|
||
The optional v2 `runtime.launch.env` field is a bounded map from the target
|
||
process environment key to a host-owned reference. Values must use the exact
|
||
form `${env:TUTTI_*}`; Tutti resolves the referenced `TUTTI_*` variable at
|
||
launch time and omits the entry when the host variable is unset. Manifests
|
||
cannot read credentials or arbitrary environment variables, and they cannot
|
||
embed literal paths or secrets in the package. Provider-specific mappings
|
||
belong in the extension manifest, while the daemon only implements this
|
||
generic, allowlisted resolution. For example, a Kimi extension can declare
|
||
`KIMI_SHELL_PATH: "${env:TUTTI_MANAGED_POSIX_SHELL}"` without adding a Kimi
|
||
branch to `tuttid`.
|
||
|
||
Discovery skips this two-link managed entry when probing PATH, then resolves it
|
||
through the managed activation record. It therefore remains `source=managed`
|
||
and retains fingerprint verification instead of being mistaken for an
|
||
independent local installation. Both links are activation integrity: a missing,
|
||
foreign, broken, or unexpectedly repointed entry produces
|
||
`runtime_integrity_failed` and an explicit reinstall plan. Existing
|
||
extension-version roots may be adopted into the runtime-identity root only when
|
||
their Tutti activation record, package identity, executable fingerprint, and
|
||
current discovery version check all match. Binary runtimes additionally require
|
||
the executable SHA-256 and byte size to match the artifact in the current signed
|
||
manifest before adoption; otherwise the candidate is ignored and the user
|
||
receives an explicit reinstall plan. Candidate enumeration, activation-record
|
||
reads and replacement, rename, rollback, and recursive cleanup operate relative
|
||
to held no-follow directory descriptors on Unix. Binary executable identity is
|
||
re-established inside the descriptor/snapshot version launcher before any
|
||
candidate can execute, and is checked again around activation-record update and
|
||
adoption rename.
|
||
|
||
Normal resolution also opens the active runtime-identity root relative to the
|
||
held workspace descriptor on every attempt, even when no recovery backup is
|
||
present. Activation metadata and executable bytes are read relative to that
|
||
no-follow root handle and the handle's path identity is checked around the
|
||
version probe. A file or symlink occupying the active-root name is rejected as
|
||
an integrity failure; resolution never follows it or adopts its target.
|
||
|
||
The managed-runtime activation record persists the resolved executable's
|
||
runtime identity, package identity, SHA-256, and byte size. Every
|
||
managed-runtime resolution recomputes both fingerprint fields before the
|
||
version or ACP probe, and recomputes them again after the version probe. Binary
|
||
runtimes also compare the bytes with the current signed artifact SHA-256 and
|
||
size and revalidate the native platform on every resolution. A replacement, an
|
||
artifact from superseded signed metadata, or even a replacement reporting the
|
||
same compatible version is rejected with `runtime_integrity_failed` and returns
|
||
the exact reinstall plan.
|
||
|
||
For a managed binary, that expected SHA-256 and size are also carried through
|
||
the generic Standard ACP adapter into the final process specification. Linux
|
||
starts the verified open descriptor directly. macOS copies that descriptor to
|
||
a freshly created daemon-private snapshot, verifies it, marks both the file and
|
||
directory immutable and non-writable, starts it, and removes the snapshot
|
||
immediately after process start. The later
|
||
process boundary therefore never resolves the replaceable managed-runtime
|
||
pathname after trusting an earlier fingerprint.
|
||
|
||
An auth-required snapshot includes normalized methods from the fresh ACP
|
||
initialize response. Authentication submission accepts only the advertised
|
||
method ID and client action ID. The daemon initializes a new
|
||
process, revalidates the method, calls ACP `authenticate` on that process, then
|
||
requires `session/new` to succeed. Only that result produces `ready`; methods
|
||
being advertised is never itself an auth verdict. Authentication actions are
|
||
durable and never persist credentials.
|
||
|
||
A signed `authentication` profile may bind one runtime-advertised method ID to
|
||
a closed terminal command declaration. Authentication profile v1 supports
|
||
`type: "terminal"` with either `command.strategy: "runtime-subcommand"` and a
|
||
bounded, non-empty argv array, or `command.strategy: "runtime-slash-command"`
|
||
with exactly one safe slash-command name and a bounded literal `readyText`
|
||
marker. For a slash command, the daemon exposes the quoted runtime launch plus
|
||
one atomic typed startup action; it never exposes raw terminal input. The
|
||
Desktop terminal adapter validates the action, generates the leading `/`, and
|
||
submits it only after the matching terminal session emits the declared marker.
|
||
Startup failure terminates setup readiness monitoring instead of leaving a
|
||
background poll running. The extension cannot declare raw terminal input,
|
||
control characters, or shell source.
|
||
|
||
A declaration may also replace the advertised method's display name and
|
||
description with bounded presentation strings. Tutti applies those fields only
|
||
when the declaration ID and type match the fresh ACP method, so
|
||
provider-specific setup wording and commands remain in the extension package
|
||
instead of daemon code. For runtime subcommands, the daemon quotes every
|
||
projected argument before exposing the ready-to-run launch command.
|
||
|
||
Without a signed declaration, Tutti retains compatibility with provider
|
||
metadata: it reads top-level ACP `type`/`args` fields first and falls back to
|
||
`_meta["terminal-auth"]`. Methods of type `terminal` require an interactive
|
||
terminal that can never complete inside the headless setup process, so the
|
||
daemon rejects ACP `authenticate` for them immediately and lets the host surface
|
||
or launch the resolved command. The profile changes terminal presentation and
|
||
launch policy only; the fresh ACP initialize response remains authoritative for
|
||
which authentication methods are actually available.
|
||
|
||
An ACP `authenticate` result may expose non-secret account identity through a
|
||
namespaced `_meta` entry ending in `/userinfo`. Setup normalizes only the user
|
||
ID, display name, organization, and selected auth method; it discards all other
|
||
metadata. A successful authentication action persists that identity with its
|
||
private action record so later ready snapshots can show the signed-in account
|
||
without repeating authentication or storing credentials.
|
||
|
||
ACP runtimes may still accept `authenticate` and `session/new` before a real
|
||
request touches provider credentials. The Agent runtime therefore feeds a
|
||
failed formal-session authentication outcome back into the shared provider
|
||
auth invalidation store. A later Target setup probe overrides an otherwise
|
||
ready ACP probe with `auth_required` until an explicit re-authentication
|
||
succeeds or a formal request completes successfully. This is outcome feedback,
|
||
not a synthetic prompt probe during setup.
|
||
|
||
Install actions are idempotent by Target/fixed-installation scope and client action ID. Their
|
||
phase and status are persisted under daemon state. A queued/running record not
|
||
owned by the current daemon process is recovered as `interrupted`, allowing an
|
||
explicit retry. Setup probes use a daemon-owned discovery CWD. Formal sessions
|
||
pass their real CWD only to ACP and launch-argument expansion; runtime storage
|
||
remains project-neutral.
|
||
|
||
Setup action ownership follows the daemon layers. `biz/agentextension` defines
|
||
the action and scope values; `service/agentextension` owns transitions and
|
||
depends on `SetupActionStore` plus `SetupDiscoveryDirectory`; the
|
||
`data/agentextension` file adapters alone derive paths and perform filesystem
|
||
I/O. Action JSON is scope-validated, decoded strictly, and replaced atomically
|
||
with private directory/file permissions. Each setup operation resolves its
|
||
discovery root once through that directory adapter, then passes the resolved
|
||
root through runtime resolution, installation verification, probing, and
|
||
authentication instead of recomputing or creating it in those workflows.
|
||
|
||
After a request accepts an install or authentication action, work no longer
|
||
depends on that request context. It runs under the setup service's
|
||
daemon-owned worker context. Daemon shutdown closes setup before the runtime
|
||
and state store, refuses new actions, cancels and waits for accepted workers,
|
||
then persists cancellation as `interrupted`. Persistence failures are returned
|
||
by setup close; they are not discarded as background goroutine errors.
|
||
|
||
AgentGUI recognizes target-runtime setup metadata on the exact Target. Its
|
||
shared panel owns explicit confirmation and plan presentation,
|
||
progress polling, runtime-advertised auth method selection, login progress, and
|
||
retry. A failed or interrupted install keeps its durable error visible while
|
||
the retained plan can start a new action with a new client action ID; refreshing
|
||
the old action is not an installation retry. Background action polling preserves the established detection result
|
||
instead of restarting the detection presentation on every snapshot request.
|
||
Failed explicit authentication keeps the provider's ACP error on the durable
|
||
action and presents it beside the localized failure summary while leaving the
|
||
auth method available for an explicit retry. Target selection never opens the dialog. Initial detection remains
|
||
non-modal and blocks the empty-home composer with a checking gate; a non-ready
|
||
snapshot shows an inline setup affordance, and only an explicit click opens the
|
||
dialog. The selected Target's config menu also exposes this same dialog after
|
||
setup is ready, including auth-method selection and explicit re-authentication.
|
||
The empty-home gate and config-menu host reuse one Target-scoped watch rather
|
||
than starting duplicate polls. Closing remains controlled, and the Dialog
|
||
stays mounted through ready transitions so its pointer/scroll lock can clean
|
||
up. Active conversations are never replaced by setup UI.
|
||
|
||
## Activation And Failure Behavior
|
||
|
||
Source defaults come from generated configuration. A source with `enabled:
|
||
true` is a stable integration and is always reconciled; historical
|
||
`agent.extension.<key>` preference values no longer override it. Sources with
|
||
`enabled: false` remain Early Access integrations that Desktop settings can
|
||
activate through the generic `agent.extension.<key>` feature flag. A preference
|
||
write reconciles only when one of those opt-in sources changes effective
|
||
activation, then the desktop refreshes its Agent Target catalog. The daemon
|
||
keeps this source-key driven rather than branching on individual providers.
|
||
|
||
Disabled Early Access sources do not perform network requests and their system
|
||
Target is removed. When an active source cannot reach its index, a previously
|
||
verified active installation remains available. If no verified installation
|
||
exists, the source is not registered and `tuttid` logs one
|
||
`agent_extension.reconcile_failed` record with a JSON payload.
|
||
|
||
Daemon startup restores and verifies every active source's local active
|
||
installation before serving Agent Target reads. A remote installation is usable
|
||
only when its version equals the current client's exact source pin; an active
|
||
record from another client pin forces source reconciliation and fails closed if
|
||
that pinned release cannot be obtained. When every active source has a usable
|
||
local installation, release-index refresh runs in the background and does not
|
||
delay the daemon listener. If any enabled source has no usable local
|
||
installation, startup keeps the synchronous reconcile path so that a first
|
||
installation is registered before the daemon serves its initial Target
|
||
catalog. Preference-driven activation changes also remain synchronous. After
|
||
activation, managed Runtime convergence continues in the background and does
|
||
not delay the daemon listener.
|
||
|
||
Agent Target catalog reads continue to verify the installed extension package
|
||
and managed runtime integrity before reporting availability. Successful runtime
|
||
version probes are reused only while the resolved executable fingerprint,
|
||
version arguments, and constraint are unchanged. Concurrent reads share the
|
||
same in-flight probe. Failed probes are not cached, so repairing or installing a
|
||
runtime is visible on the next read. Runtime launch and setup keep their
|
||
authoritative integrity checks and do not trust catalog availability as launch
|
||
authorization.
|
||
|
||
Composite session-pinned adapter cache keys, richer tool/event profiles, and
|
||
removal of remaining built-in catalogs remain migration work. Composer
|
||
discovery is not setup state and does not infer
|
||
installation or authentication readiness. Client-pinned remote Extensions use
|
||
an automatically managed Runtime with compatible local fallback while it is
|
||
being installed; local development Extensions remain local-first. `tuttid`
|
||
never installs into a user project.
|
||
|
||
## Declarative Grok Compatibility
|
||
|
||
The generated source registry contains a default-off `grok` entry using key ID
|
||
`tutti-grok-release-v1` and the approved Ed25519 public key. Enabling remains a
|
||
generic `agent.extension.grok` feature-flag decision; no Grok provider or
|
||
renderer branch exists.
|
||
|
||
The separate Grok extension is expected to declare official CLI `0.2.103` for
|
||
`darwin-arm64` as a binary artifact with URL
|
||
`https://x.ai/cli/grok-0.2.103-macos-aarch64`, size `121600480`, SHA-256
|
||
`1be9de92f31566f2d38992125f902220b022f4f1e3fb7330532a0513d1d6f0f2`, and
|
||
official provenance `https://x.ai/cli/install.sh`. Its launch executable is
|
||
`${installRoot}/grok`, `publishUserCommand` is `false`, and managed launch args
|
||
are exactly `--no-auto-update --permission-mode ${permissionMode} agent stdio`.
|
||
They remain extension-owned declarative data. Only Apple Silicon is initially
|
||
listed; the generic catalog can add independently pinned platform entries in a
|
||
later signed extension release.
|
||
|
||
For Grok CLI 0.2.103, the spawn-time permission values are `default` for
|
||
ask-before-write, `auto` for automatic operation, and `bypassPermissions` for
|
||
full access. These are CLI permission values, not ACP workflow mode IDs; Plan
|
||
continues to use the independent `plan` / `default` `session/set_mode` pair.
|
||
|
||
## Opt-in Real Grok ACP Smoke
|
||
|
||
The generic adapter has an explicit local smoke for an already installed and
|
||
authenticated official Grok CLI:
|
||
|
||
```sh
|
||
TUTTI_REAL_GROK_ACP_SMOKE=1 \
|
||
TUTTI_GROK_BIN=/absolute/path/to/grok \
|
||
go test ./packages/agent/daemon/runtime -run TestRealGrokACPInitializeSmoke -count=1
|
||
```
|
||
|
||
It is skipped by default, reads no user authentication state in CI, creates no
|
||
ACP session, and sends no prompt. When explicitly enabled it verifies the
|
||
executable/version, performs standard ACP `initialize`, checks that the
|
||
initialize model state has an available and current model, then closes the
|
||
process. The fake ACP tests separately verify the exact declared spawn argv and
|
||
all permission tiers; keeping those checks separate lets the smoke remain
|
||
non-billable even when a CLI release makes `session/new` contact the service.
|
||
|
||
This support does not add a Grok provider, icon, executable wrapper, credential
|
||
persistence, private authentication protocol, headless/TSH integration,
|
||
automatic CLI updates, or private `x.ai/*` operations. End-to-end AgentGUI
|
||
visibility still requires separately reviewed and signed external extension
|
||
bytes; the default-off source alone does not make a release available.
|