Local services
A desktop miniapp can ship a native HTTP service inside its package. The AI
Platform verifies the locked service files, starts the service in a reviewed
sandbox, waits for its readiness endpoint, and returns a host-selected loopback
origin through sdk.services.v1.
Use a local service only when browser or QuickJS code cannot perform the work. The service is part of the signed package release. It is not a general process launcher, and miniapp code never receives a process handle or chooses a port.
Declare the service and its authority
Set compatibility.tapHost to a range that excludes hosts before 2.0.5. Keep
compatibility.tapSdk pinned to the exact SDK version used to build and test the
package. The examples below use SDK 0.5.3.
Add three declarations to manifest.tap.json:
- the canonical
local-service.runaction; - one
local.servicecontribution with an exact effect naming itself; and - the same action and effect on each desktop surface that calls the service.
This fragment shows a service named document-indexer-service. Merge it into a
complete descriptor generated by create-tap-miniapp.
Keep the starter's lifecycle contract alongside these contributions. A desktop
surface selecting ./ui/desktop with the webview runtime requires
lifecycle.lifecycleExpose, a matching target expose such as
./tap/lifecycle, and the same webview runtime on that lifecycle expose. SDK
0.5.3 validates this relationship before packaging. Contribution IDs such as
document-indexer-service may use ASCII letters, digits, underscores, and
hyphens; dotted IDs are rejected.
Copy the complete service runtime into the desktop target output under
runtimeRoot before assembleTapPackage runs. Assembly locks every regular
file in that subtree and rejects missing executables, symlinks, unsafe paths,
and collisions.
The host replaces only these launcher placeholders:
Launcher arguments and environment variable names are specific to your
executable. Do not bind a service to 0.0.0.0, inherit the host environment, or
accept arbitrary Host and Origin values.
local-http-service is the general HTTP sandbox. The
apple-simulator-control profile is available only for reviewed macOS services
that need CoreSimulator access. Declare a launcher for every desktop target you
support. The SDK rejects an Apple-only sandbox profile on another platform.
Package the Baguette reference service
Baguette v0.1.85 is the reference integration for an Apple Silicon macOS
service. It is an upstream project, not a miniapp product owned or shipped by
The AI Platform. Use the pinned release as a packaging example for services that
need CoreSimulator.
The pinned integration requires:
- Apple Silicon macOS 15;
- the full Xcode application installed in
/Applicationsand selected withxcode-select; - the iPhone Simulator platform and at least one available simulator device; and
- Miniapp SDK
0.5.3withcompatibility.tapHostset to>=2.0.5.
Check the machine before testing the package:
uname -m must report arm64. The selected developer directory must belong to
a supported Xcode bundle in /Applications, and simctl must list an available
device. Command Line Tools alone do not provide CoreSimulator.
Download and verify the exact upstream archive before extracting it into the package source tree:
The release archive supplies the executable and runtime resources, but it does
not contain license or provenance files. Before assembly, add the upstream
Apache-2.0 LICENSE, the notices and license texts required by the resolved
Swift, web, and virtual-camera dependencies, and a machine-readable provenance
record under runtimeRoot. Record at least the source URL, release commit,
archive name, byte count, SHA-256 digest, and resolved dependency versions.
Review these files for the package you publish; do not copy TAP's test-fixture
notice verbatim.
Keep the complete Swift bundle, web assets, virtual-camera library, and those
legal and provenance files under runtimeRoot. Extract the archive before
assembly. Do not package the source archive, download Baguette at runtime, or
replace the pinned bytes through a mutable URL.
Add this contribution after declaring the canonical local-service.run action
and exact service effect described above:
Assembly locks every extracted file and verifies that Baguette exists inside
the declared runtime root. The package intentionally has no launcher for Intel
macOS, Linux, or Windows, so those hosts report
local_service.unsupported_platform.
Start Baguette and read its readiness resource through the returned origin:
The host supplies the loopback bind address, port, and exact package-origin
hostname. Baguette uses that hostname for its Host and Origin checks. Do not
retain running.endpoint.origin after the service generation changes.
Start and call the service
Feature-detect sdk.services.v1 because mobile and older desktop hosts do not
install the capability.
ensureRunning is idempotent. It installs and starts the declared service when
needed, waits for the signed readiness deadline, and returns the exact origin
for the current generation. getStatus reads state without starting or
restarting anything.
The HTTP origin can also host service-defined WebSocket routes:
Do not retain an origin across a status change. A restart creates a new opaque generation and may select a new port.
Test the service in Test Lab
Native services are disabled in Test Lab by default. Enable them only on a
Live TAP profile with the exact service effect. The matrix entry must also
declare action:local-service.run.
The descriptor vocabulary uses "mode": "live". Inside the attached Rstest
session, the canonical SDK projection is tap.mode === "live-tap"; do not
compare the runtime fixture with the descriptor literal.
Run the descriptor checks before connecting the project:
Open Settings > Miniapps > Installed, select the package, and open Tests. Connect the source and emitted package roots, then run the Live TAP row. The authority review shows Native services as Enabled for this session only and lists the exact action and service effect.
Test Lab copies and re-verifies the locked package assets for that run. Service calls use the session supervisor and never create a production grant. The host stops the complete service process tree and removes the session materialization when the run finishes, is canceled, or its package snapshot changes. If the bound build changed, reconnect the test project before running it again.
The Rstest adapter observes browser requests for diagnostics but does not
intercept them. A host-selected local-service loopback origin cannot be listed
ahead of time in requiredOrigins; leave that list for exact, static
sdk.http.request origins. The native-service action and effect authorize the
launch, while the packaged service must still enforce the host-provided Host and
Origin values on its HTTP routes.
An Untrusted package can use this reviewed test session. It remains denied from production local-service authority.
To cover denial behavior, use a separate profile with nativeServices: false,
permissionScenario: "deny:local-service.run", and
deniedActions: ["local-service.run"]. A profile that enables native services
cannot deny the same action.
Inspect and restart an installed service
Open Settings > Miniapps > Installed, select the package, and stay on Overview. The Local services panel shows each contribution's supported platform, current state, generation, and stable error code.
Choose View local logs to read the bounded tail for the current generation. The tail stays on the device, indicates when older output was truncated, and is never uploaded automatically.
Choose Restart for a stopped, running, or failed service. Restarting interrupts current miniapp connections. The host re-verifies and rematerializes the active release before starting a new generation.
Common failures include:
Treat unknown local_service.* codes from newer hosts as ordinary operation
failures. See Troubleshooting
for package activation, CoreSimulator, readiness, and crash-loop checks.