Sharing an App
Export a Kortecx App to a single portable .kxapp file, import it under your own account, or clone it locally — and understand exactly what travels and what never does.
An App is the saved, re-runnable unit of work in the agentic runtime — see Apps. Sharing one means writing it to a single file and handing that file over: attach it to an email, drop it in a shared folder, commit it to a repository.
The file is called a bundle, and it ends in .kxapp.
Two things are worth knowing before you send one.
A bundle carries no authority. No secret, no password, no API key, and no permission travels inside it. When someone imports your App, it runs under their account with their permissions — never yours.
A bundle is not signed. There is nothing in the file that proves who made it. Read what is inside before you run it.
Why the plain envelope is not enough
An App stores its text — prompts, rules, context, skill instructions — by reference. The saved App holds a pointer to each piece of content, not the content itself. That is efficient locally, and useless on someone else's machine, where those pointers resolve to nothing.
kx app export apps/local/echo-demo --output echo.app.jsonThat writes the envelope: the App's structure, by reference. It round-trips on your own machine. It is not self-contained.
A bundle fixes that. It is a kortecx.appbundle/v1 archive: the App's envelope plus every blob of content it points at, packed into one file.
Export an App
Find the App's handle
kx app listA handle looks like apps/local/echo-demo. That is what every command below takes.
Write the bundle
kx app export apps/local/echo-demo --bundle echo.kxappimport kortecx as kx
a = (
kx.app("echo-demo")
.blueprint(kx.flow().agent("Echo the user's input."))
.rule("no-pii", body="Never reveal personal data.")
)
a.export("echo.kxapp", bundle=True, client=kx.default_client())bundle=True needs a client, because collecting the content closure means talking to the running server. It saves the App first, then writes the file.
import { app, flow } from "@kortecx/sdk";
await app("echo-demo")
.blueprint(flow().agent("Echo the user's input."))
.rule("no-pii", { body: "Never reveal personal data." })
.export("echo.kxapp", { bundle: true });bundle: true needs a client, because collecting the content closure means talking to the running server. The Node entry point (@kortecx/sdk) supplies a default one; pass { bundle: true, client } to use your own. It saves the App first, then writes the file.
The command prints how many blobs it packed and how many bytes they came to.
Include corpora, if the App grounds on one
By default the bundle skips dataset payloads, because a corpus can be far larger than the App around it. Add --with-data to pack the documents in too:
kx app export apps/local/analyst --bundle analyst.kxapp --with-dataNow the App carries its own reading material. Whether it grounds on that reading material on the other side depends on the receiving runtime:
- Retrieval requires a gateway built with the
hnswfeature. Without it, the run honest-degrades to an ungrounded one rather than failing. - Materializing the carried corpus on the first run also needs a served embedding model, and the corpus must fit under the self-ingest ceilings — at most 4096 documents and 64 MiB. Over either ceiling, the corpus is skipped and the App grounds on an existing dataset of that name, or on nothing.
kx app importreports this at import time, while you can still split the corpus or ingest it by hand.
See Datasets.
Export refuses to ship something that looks like a secret
Blob bodies travel verbatim, so before writing a bundle the exporter scans them for things that look like credentials — a PEM private-key block, an AWS-style access key id. If it finds one it prints the offending blob's content address and refuses. Read the content, remove the secret, and re-export. If you are certain it is a false positive, --force proceeds.
This is a high-signal check, not a guarantee. It cannot recognise every shape a secret can take. You are still the one responsible for what is in your prompts.
What travels, and what does not
Connections and secrets never travel
An App that reaches out to an external service carries only the name of the credential it wants — never the credential. There is no field in the format that can hold one.
The person importing the App re-registers that connection themselves, under their own account, with their own credentials:
kx connections add --provider gmailUntil they do, the App fails closed at run with a message naming what is missing:
missing integration: KX_GMAIL_CREDENTIAL (register it with `kx connections add`)That is the designed behaviour, not a bug. See Connections.
Travels in the bundle:
- the App's structure — its blueprint, steering settings, and budgets
- the text it carries: prompts, rules, memory artifacts, skill instructions
- context blobs it references
- dataset payloads, but only with
--with-data
Never travels:
- secrets, API keys, tokens, passwords — any credential value
- registered connections (only the credential name the App asks for)
- your grants, permissions, or policy
- run history, results, and cost records — the format has no field for any of them
- any file on your host filesystem
Import a bundle
Importing reconciles the file under your own account.
Import an unfamiliar bundle from the CLI
The console has an Import button in the Apps section header, and it imports the file you pick straight away. Only the CLI shows you the instruction text the bundle carries before writing anything. If you did not author the bundle, import it with kx app import and read the review.
Run the import
kx app import echo.kxappRead the review
Before anything is written, the importer prints the App's name, its description, and the full text of every prompt, rule, and skill instruction the bundle carries — not a list of names. It then lists the connections and tools the App is asking for.
Read it. Imported instructions steer a model that runs with your tools. The text is the thing worth reviewing; a name-only summary would tell you nothing.
Then it asks:
Proceed with import? [y/N]Confirm
Answer y. In a script, where there is no terminal to ask, the import refuses unless you pass --yes — so nobody can slip an import into a pipeline without a human having looked once:
kx app import echo.kxapp --yesRegister what did not travel
The import ends by listing the integrations you still need locally — connection credentials, tools, datasets that need re-ingesting. Work through them, then check your standing with:
kx app manifest apps/local/echo-demoThat diffs what the App wants against what you actually have, marking each capability satisfied, missing, or inherited. It is read-only and gates nothing; the runtime enforces the same intersection at run. See Apps.
An import will not quietly overwrite an App you already have at the same handle. It stops and tells you; --force overwrites deliberately.
The checks an import runs
- Schema. A bundle tagged with a version this build does not understand is rejected outright.
- Shape. Every digest and content reference must be 64 characters of lowercase hex; every blob must be valid base64.
- Digest match. The importer re-derives the App's digest from the envelope and compares it to the one the bundle declares. A mismatch means the file was corrupted or altered, and the import stops.
- Size ceilings. At most 4096 blobs, and at most 512 MiB of content across the whole bundle, checked before a single byte is uploaded. Separately, the server caps any individual blob — 32 MiB by default.
- Content integrity. Each blob is uploaded and the server re-derives its address from the bytes themselves. If the address it computes differs from the one the bundle claimed, the import stops. Blobs you already hold are recognised and not stored twice.
- Envelope validation. The server re-validates the envelope on save, exactly as it would for an App you authored yourself.
`source_digest` is a lineage hint, not a signature
A freshly exported bundle carries no lineage field. It is the importing side that stamps one: when you import a bundle, the App saved under your account records a source_digest — the digest of the App it came from, taken from the envelope the importer just verified. A clone records the same thing, using the source App's digest. It is useful for answering "where did this copy come from" when you already trust the source.
It is not a signature. It is a hash of an App's own structure, and nothing binds it to a person. Kortecx has no signing, no publisher identity, and no notion of a trusted source in this release. Provenance across parties you do not already trust is unavailable — treat a bundle from a stranger exactly as you would treat a script from a stranger: read it first.
Importing can never widen what you are allowed to do
This is the guarantee worth stating on its own.
An App carries a request for capability — the tools it wants, the connections it wants, the model it wants. The runtime grants only the overlap between that request and what you are already permitted to do. A request is never authority.
So an imported App:
- runs under your account and your permissions
- resolves every tool grant against your policy, at run time, server-side
- resolves each connection against your registered credentials, by name
- fails closed if it names a model this instance does not serve, rather than quietly running on a different one
Nothing in the file can change any of that. There is no field for a permission, no field for a grant, no field for a secret. Sharing an App is sharing a plan, never a key.
Sharing between people, not between machines
This is single-system sharing: you hand someone a file, they import it into their own runtime. There is no registry and no link to send. Multi-party sharing on one instance, and provenance you can verify between parties, do not exist in this release.
Clone an App locally
To make a copy of one of your own Apps under a new name — a safe place to experiment, or a starting point for a variant — clone it. Nothing is exported and nothing transfers, because the content is already on the machine:
kx app clone apps/local/echo-demo my-copyThat writes a new App at apps/local/my-copy, records the original's digest as its lineage, and leaves the original untouched. If an App already exists at the new name, the clone stops and asks you to pick another.
The console calls this Duplicate, in each App's overflow menu.
The file itself
A .kxapp is plain text — canonical JSON with sorted keys, and blob bodies base64-encoded. You can open it in an editor and read it.
Because the format is canonical, the same App exported from the CLI, from Python, or from TypeScript produces byte-identical files. That is pinned by a shared golden corpus the three implementations are tested against, so the surfaces cannot drift apart.
The top-level fields:
| Field | What it is |
|---|---|
schema | kortecx.appbundle/v1 — the version gate; a reader fails closed on anything else |
app_digest | the App's own identity, derived from its envelope |
envelope | the App's structure, verbatim |
blobs | the content closure — each blob's address mapped to its bytes |
source_digest | optional lineage hint; omitted when there is none, as on a fresh export |
An App with no carried content and no lineage omits blobs and source_digest entirely.
Next
Editing an App
Open an App's files and type, or describe the change in a sentence — every proposed change is shown as a before/after per file, nothing is applied until you accept, and one click rolls it back.
Workflows and Chaining
Compose agents into a workflow with the chain operators, save it as a portable blueprint, and run the swarm, supervisor and consensus multi-agent patterns.