Bolt Apps

Bring your daily workflows into Bolt.

Wherever you are, terminal or browser or a specific app, Bolt is one keystroke away. Bolt Apps are integrated, inline access to the first-party and third-party systems you depend on, without leaving where you are. Two tiers, one schema. Admin Apps connect what your org already runs. User Apps run on your own machine, under your own identity. Same trust contract for both, enforced at exec time.

The two tiers

Same template. Different boundary.

Both tiers are YAML manifests. Both show up in Bolt as keyboard shortcuts users summon by typing. The runtime, the install path, and the trust posture differ. Read them side by side.

Admin Apps

Bring your stack

Org admin enables centrally. Everyone in the company gets it in Bolt in one move. Routes through the customer's Bolt service against the systems the org has connected.

Who authors
The customer's admin (or Sparcle, for bundled integrations)
Who installs
Org admin enables in Settings; per-user OAuth on first use
Where it runs
Inside the customer perimeter, via the customer's Bolt service
What hits the network
Org-connected systems (Jira, Stripe, Confluence, internal APIs, ...)
Whose identity it runs as
Per-user OAuth, scoped to the calling user
What Sparcle servers see
Nothing. Sparcle has no servers in any customer request path.
Audit
Full call, emit, action chain. Same chain as the rest of Bolt and Aeira.
Removable
Admin disables in Settings; per-org policy

User Apps

Bring your shell. Bring your script. Bring your tools.

An individual drops a .yaml in a local folder. Only they have it. Runs as their OS identity on their own machine. Sparcle servers are not in the request path. Admins can disable the entire tier on managed devices.

Who authors
The organization's employee or team
Who installs
User drops .yaml in a local folder, clicks Reload
Where it runs
On the user's own machine, end-to-end local
What hits the network
Whatever the user's script asks for. With the user's auth, from the user's machine.
Whose identity it runs as
The user's OS identity. Same as their terminal.
What Sparcle servers see
Nothing. Sparcle has no servers in any customer request path.
Audit
Optional metadata ping per call. No body, ever.
Removable
User deletes the file; admin can disable the whole tier on a managed device

The User App disclosure

What a User App is, in four sentences.

This is the wording Bolt's Settings page shows the user when User Apps are enabled. It is also the wording we hold ourselves to on every other surface that describes the tier.

User Apps run, evaluate and process data that user authors on user's machine, under user's identity. No AI in the path. No Sparcle in the path. Read declarative YAML before you enable.

The trust contract

Five properties the runtime enforces.

None of these are author-configurable. A manifest that tries to opt out of any of them is rejected at load time. A manifest that tries to smuggle past them at runtime never gets the chance: the executor and the row builder both stamp these on every output regardless of what the YAML asks for.

Confirm before run

Bolt renders a 'Press Enter to run' row instead of firing on every keystroke. Authors can customize the prompt copy but cannot turn confirm off. Applies to admin and user tiers alike, with an auto_when escape valve for unambiguous queries (e.g. exact Jira issue keys).

Never an LLM tool

Apps are not registered as agent tools and never appear in the LLM's tool-use surface. The agent loop does not know they exist. A poisoned document cannot tell the model to run an App because the model cannot run anything.

Never staged into chat context

App output never auto-attaches to a chat message. The chat composer's attachment surface accepts entity and plugin context, not App rows. Output stays in the dropdown and the right-side preview pane. The LLM only sees it if the user copies and pastes it.

argv-only execution (User Apps)

Templated commands are tokenized into argv via POSIX shell rules and spawned directly. There is no /bin/sh -c. Multi-statement shell, pipes, command substitution, and backticks are rejected at validate time, before the manifest ever loads.

Forced safety fields

Every row a User App emits gets canEscalateAi=false, source=user_bridge, and pii_boundary=enforce stamped on it regardless of what the YAML asks for. Forbidden right-pane kinds (entity-graph bindings on a User App) are rejected at load time so a manifest cannot graft itself onto Bolt's entity router.

The testable claim

Bolt does not multiply the user's authority.

A User App runs commands the user could already have run from their terminal. That is the strong version. The four cards below are the specific things a user's terminal does NOT let happen that Bolt also does not let happen.

Bolt does not let an LLM invoke a User App

There is no agent-tool path to user_util_invoke. Architecturally, not as a flag a future release could flip. A model that wanted a User App to run would have to find a human to open Bolt, type the App's keyboard shortcut, and press Enter.

Bolt does not let a poisoned response chain a User App

The action buttons rendered for an App row come from the manifest, not from the model. The model cannot inject a new action, a new url, or a new shell command into a row it sees.

Bolt does not store User App output it does not own

The optional audit ping is metadata. The query, the arguments, the stdout, the stderr: none of it leaves the user's machine via Sparcle. If the script writes to a file, that is the user's file. If it hits an external API, that is the user's request from the user's IP.

Bolt does not run User Apps as root

The Bolt App Runtime runs as the user. User App commands inherit that identity. There is no privilege escalation path; the runtime does not have privilege to escalate to.

Admin disable

On a managed device, admins can turn User Apps off.

Settings, Utilities, User Apps. One toggle. When off, installed Apps stop loading, Bolt stops surfacing them as suggestions, and invocation refuses at the runtime boundary. Manifest files on disk are kept so re-enabling restores the user's setup identically.

Shipped today

Device-level toggle

The Allow User Apps toggle is per-device. An admin who manages the OS image (MDM, JAMF, Intune) can pre-write the policy file to disabled before the user ever opens Bolt.

Path: <app_data>/user-apps-policy.json with { "enabled": false }.

Coming

Org-pushed policy

The same toggle, propagated from the admin tenant down to every device on first connect. Removes the need to write the policy file via MDM and gives the admin a single dashboard view of which devices honor the gate.

Until that ships, admins who need org-wide enforcement use the MDM path above.

Why the gate matters: a User App runs as the user, with the user's auth. Its outbound traffic does not go through the admin-tier PII masking layer that the rest of Bolt enforces. On a managed device carrying regulated data, an admin may want the tier off. This is not a runtime hole. It is the same authority the user already has from their terminal. The gate exists so admins can decide.

Author a User App in 25 lines

A few lines of YAML. No data crossing a boundary it should not.

The example below installs an App that runs git log in a local repo and shows the commit lines in Bolt. Drop it in your user-apps folder, click Reload, then type =git-log to try it. Same schema serves Admin Apps too, with a different runtime block.

schema_version: 1
runtime: bridge
id: git-log
chip: git-log
title: Recent commits
icon: git-branch
description: Show recent commits in a local git repo under ~/dev
placeholder_examples: ["my-app", "infra 30"]
emits: Generic

bridge:
  command:
    macos:   "git -C ~/dev/{{repo}} log --oneline -{{limit | default: 20}}"
    linux:   "git -C ~/dev/{{repo}} log --oneline -{{limit | default: 20}}"
    windows: 'git -C C:\dev\{{repo}} log --oneline -{{limit | default: 20}}'
  parse: lines
  timeout_seconds: 5

args:
  - { name: repo,  from: "query.word(0)" }
  - { name: limit, from: "query.word(1)", default: 20 }

presentation:
  widget: list
  title_field: line

actions:
  - { id: copy, kind: composer, target: row, label: Copy SHA, insert: "{{line}}" }