Bundles
A bundle is a versioned file that lifts a working configuration out of one instance and puts it into another: an agent, an MCP connector, an app, or a knowledge set. Export produces a YAML or JSON document; install creates the resource from it and records exactly what it wrote — so a later version can be merged in three-way without overwriting local edits.
Export and install work and are supported, but they are not yet stable:
- The file format may change between releases. A bundle is meant for moving and provisioning, not as a long-term backup. Re-export rather than keeping old files around.
- There is no registry. Bundles are files you move between instances yourself.
- Updating is API-only. When a new version and a local edit touched the same field the route reports the conflict and overwrites nothing — that case still needs an interface.
- Uninstalling deletes. The resources the bundle created are removed; only what you have edited locally since installing is kept.
The routes are not all protected the same way:
GET /api/agents/{id}/bundleandGET /api/widgets/{public_id}/bundleare reachable by any signed-in caller holding the relevant permission, exactly like the routes beside them (GET /api/agents/{id}/exportand the rest of the app catalog). The handler enforcesagent:create/agent:update/agent:deleteandapp:managerespectively; the app export is additionally scoped to what the caller can see, so another user's private app cannot be exported.GET /api/knowledge-sets/{id}/bundlerequiresknowledgeset:manage— the same permission as every other knowledge-set route. A set an agent or a thread created for itself cannot be exported, and neither can one that has been deleted.GET /api/tool-references/{id}/bundleand the five/api/bundles/*routes (install, update, list, drift, uninstall) are restricted to instance administrators.
What a bundle never contains
This is the load-bearing property of the format, not a side effect:
- An agent's
envvalues are blanked. Only the name and description travel. - MCP header values must be
${VAR}placeholders (optionally preceded by an auth scheme such asBearer). Anything else is blanked and reported as a warning. - Reserved fields are dropped:
system,systemAccessFeature,systemAccessAdminOnly,default,alias,tokenLimits. - A knowledge bundle carries no documents. It describes the sources a knowledge set draws on; the corpus itself still travels by knowledge-set export and import.
- Whatever ties a knowledge source to the exporting organisation does not
travel: login names (
usernamefor WebDAV, Nextcloud and SMB,userfor SFTP), the Windowsdomain, the OneDrive shared links, and the S3endpoint. The endpoint selects which object store is contacted — left in place, a re-pointed bucket name would still be read from the exporting organisation's server. All of them are set afresh on the target instance. - Credentials, threads, runs, memories, team and workspace data are never part of the projected structure in the first place.
Most of these rules are applied again on install, so a hand-written bundle cannot
get around them: reserved fields are dropped again, and an MCP header carrying a
literal value instead of a ${VAR} placeholder is blanked and reported again.
One exception: an agent's env values are not re-blanked on install.
Export always blanks them, but a hand-written bundle that carries an env value
is installed with that value. Update is different — there the value configured on
this instance is kept, because env values count as instance-local.
Endpoints
| Method | Path | Purpose |
|---|---|---|
GET | /api/agents/{id}/bundle | Export an agent |
GET | /api/tool-references/{id}/bundle | Export an MCP connector |
GET | /api/widgets/{public_id}/bundle | Export an app (latest unarchived version) |
GET | /api/knowledge-sets/{id}/bundle | Export a knowledge set (sources only, no corpus) |
GET | /api/assistants/{assistant_id}/projects/{project_id}/bundle | Export a project as a composite bundle (see Project bundles) |
POST | /api/bundles/install | Install a bundle |
POST | /api/bundles/installations/{id}/update | Move an installation to a new version |
GET | /api/bundles/installations | List installations |
GET | /api/bundles/installations/{id}/drift | Report local edits |
DELETE | /api/bundles/installations/{id} | Uninstall |
Export accepts ?format=yaml (default) or ?format=json, plus ?id= and
?version= to set the bundle identity. Without them you get local/<name> at
version 0.0.0.
Example: agent
The output of GET /api/agents/{id}/bundle, trimmed of empty fields: the agent
payload also carries about fifteen null and "" entries, omitted here for
readability. Every field shown matches the real output exactly and is pinned
against it by a test. A real export's metadata also carries a provenance
block (instance, user, timestamp); without ?id=/?version= the defaults are
local/<name> and 0.0.0. Note value: "" — the env value
that was set on the agent was blanked on export.
apiVersion: bundle.basepeak.ai/v1
kind: Bundle
metadata:
id: bp/bauamt-assistent
version: 1.0.0
spec:
type: agent
payload:
name: Bauamt Assistent
description: Beantwortet Fragen zu Bauanträgen.
prompt: Du bist ein hilfsbereiter Assistent der Bauverwaltung.
model: m1abc
tools:
- office-bundle
env:
- name: REGION_CODE
description: Regionalschlüssel
value: ""
existing: false
requires:
- kind: model
ref: m1abc
policy: substitute
- kind: tool
ref: office-bundle
policy: strip
Example: MCP connector
Bearer ${MCP_HEADER_0} survives; the X-Leaked header carried a literal value
and was blanked.
apiVersion: bundle.basepeak.ai/v1
kind: Bundle
metadata:
id: bp/nextcloud-connector
version: 1.0.0
spec:
type: connector
payload:
name: nextcloud-mcp
reference: mcp://nextcloud-mcp
bundle: true
toolMetadata:
name: Nextcloud
mcpServer:
url: https://cloud.example.test/mcp
headers:
- name: Authorization
value: Bearer ${MCP_HEADER_0}
- name: X-Leaked
allowedTools:
- list_files
- read_file
A connector carrying mcpServer.auth (the universal OAuth adapter) is refused
on install — it would otherwise look configured and silently fail to authenticate.
Example: app
document is an embedded JSON object, not a string.
apiVersion: bundle.basepeak.ai/v1
kind: Bundle
metadata:
id: bp/antragsstatus
version: 1.0.0
spec:
type: app
payload:
name: Antragsstatus
description: Zeigt den Status eines Antrags.
document:
root: r
elements:
r:
type: Card
state: {}
Example: knowledge
A knowledge bundle describes sources, not content. Federal and state law
travel verbatim — every instance ingests the same statutes from the same
source, which is what makes a sector package installable at all. A
tenant-specific address is declared as substitute under spec.requires
instead: bucket (s3:), host (sftp:), server (smb:), base URL (webdav:,
nextcloud:) — and the host of a council information system (ris:).
A RIS belongs to exactly one municipality. Travelling verbatim, it would have the installing administration ingest the exporting one's council documents into their own knowledge set — publicly available, and entirely wrong. That is why a RIS host is substitutable like a bucket.
A website source's URL list (websiteCrawlingConfig.urls) does travel verbatim:
a list cannot be expressed as a single substitutable reference. It is visible in
the payload — check it after installing, or your instance keeps crawling the
exporting organisation's site.
apiVersion: bundle.basepeak.ai/v1
kind: Bundle
metadata:
id: bp/bauamt-wissen
version: 1.0.0
spec:
payload:
dataDescription: Bebauungspläne, Satzungen und einschlägige Gesetze.
flowBlueprint: bpai
name: Bauamt-Wissen
sources:
gesetze-1:
gesetzeConfig:
selectedLaws:
- baugb
- bauo-nrw
syncSchedule: 0 3 * * *
s3-1:
s3Config:
bucket: acme-docs
prefix: satzungen/
region: eu-central-1
requires:
- kind: knowledgeSource
policy: substitute
ref: s3:acme-docs
type: knowledge
Install it with a mapping of the same shape:
curl -X POST https://<instance>/api/bundles/install \
-H 'Content-Type: application/json' \
-d '{"bundle": "<contents of the yaml file>",
"substitutions": {"s3:acme-docs": "s3:dormagen-docs"}}'
Only the address itself is replaced. prefix, region, port, dir and
share travel verbatim and can be changed after install like any other
setting — the same folder layout under a different bucket is the ordinary case,
and demanding a mapping for each of those fields would hold the install up on
input nobody needed to change.
The keys (gesetze-1, s3-1) are a source's identity across instances: a later
version is merged against them source for source. A real export assigns them as
<type>-<short hash>. A source you add yourself after the install carries no
such key: it is yours, it does not count as local divergence, and an update
leaves it untouched. Uninstalling is then refused — deleting the set would
take your own source with it, so remove it first or keep the set.
Dependencies: strip, block, substitute
Every external reference is listed under spec.requires with a policy:
strip— if it is missing, drop it and report a warning.block— if it is missing, the install fails.substitute— the administrator must supply a local equivalent at install time. Models, model providers, an app's tools and the addresses of tenant-specific knowledge sources are classified this way because they are instance-specific.
For a knowledge source (kind: knowledgeSource) the instance checks only the
shape of the mapping — its type, and a non-empty address. Whether a bucket
exists and is readable from here is answered by the first sync; a check at this
point could only ever say yes.
curl -X POST https://<instance>/api/bundles/install \
-H 'Content-Type: application/json' \
-d '{"bundle": "<contents of the yaml file>",
"substitutions": {"m1abc": "m1xyz"}}'
Updating, drift, uninstalling
An installation remembers the exact bytes it wrote. An update is a three-way merge against them:
- If nothing was changed locally, the new version applies cleanly.
- Local edits to fields the update did not touch survive.
- On a genuine conflict nothing is written and the response is
409with the conflict list. Resolve withonConflict: "keep-mine"or"take-theirs".
GET .../drift reports local divergence as JSON Pointer paths. DELETE removes
what the installation still owns unmodified, leaves locally edited resources in
place and reports them. Apps are archived rather than deleted, so running
instances keep working.
In the UI
Every endpoint is also reachable from the admin area.
Exporting — wherever the resource is managed:
| Type | Location |
|---|---|
| Agent | Agent detail page → Export → Export as bundle (with dependencies) |
| MCP connector | Tools → select the connector → Export as bundle |
| App | Apps → row action Export as bundle |
| Knowledge | Knowledge Sets → row action Export as bundle |
The agent export still offers the existing manifest formats (YAML/JSON). A manifest
and a bundle are different artifacts: only a bundle installs through
POST /api/bundles/install.
If the export blanked or dropped anything, a dialog says so. That is the only place you find out — the bundle file itself looks unremarkable.
The install dialog does not yet collect substitutions. A bundle carrying a
tenant-specific address — any S3, SFTP, WebDAV, Nextcloud, SMB or RIS source,
and equally an agent with a model reference — is therefore installed through
POST /api/bundles/install as shown above. A bundle whose sources are all
public installs from the UI.
Installing and managing — under Bundles (instance administrators only): install, list installed bundles, detect local edits (drift), and uninstall. Uninstalling removes the resources it created, provided they are unchanged since the install; locally edited ones are left in place and reported.
On instances with the deletion bin enabled a knowledge set goes there
rather than being destroyed; without it the deletion is final, including the
documents already ingested. While an agent or a thread still uses the set the
uninstall is refused — the same protection DELETE /api/knowledge-sets/{id}
enforces.
Limits of this phase
Beyond the caveats above:
agent,app,connector,knowledgeandproject.toolConfiganddataProtectionparse but are refused on install.- Nested bundles (
spec.includes) are only allowed inside aprojectbundle, where they carry its agent, knowledge sets, connectors and app. See Project bundles. - A file without
metadata.idinstalls but cannot later be updated.