Skip to content

The .ajutant bundle format

Client AdminDeveloper Manage distribution Since 0.1.0

This page describes the on-disk format of an .ajutant bundle: the signed file produced when you export an assistant and consumed when you import one. It’s the single source of truth for moving assistants between environments. If you only want to move an assistant through the UI, the export/import how-to is all you need. This reference is for maintainers and integrators who build, validate, or inspect bundles directly.

A bundle is a JSON object with a signature and a payload. The signature protects the payload’s integrity; import rejects any bundle whose signature doesn’t verify against a trusted public key.

{
"signature": {
"algorithm": "RS256",
"signedAt": "2026-08-18T10:00:00.000Z",
"signedBy": "platform@ajutant.ai",
"publicKeyId": "ajutant-signing-2026",
"value": "<base64url RS256 signature>"
},
"payload": { "...": "see below" }
}
Signature fieldMeaning
algorithmAlways RS256 (RSASSA-PKCS1-v1_5 over SHA-256).
signedAtISO 8601 timestamp the payload was signed.
signedByIdentifier (email or system name) of the signer.
publicKeyIdWhich public key to verify against, see Signing keys.
valueThe base64url-encoded signature.

The signature is computed over the base64url encoding of the payload’s JSON. Because of that, a bundle can be written in either of two equivalent shapes, and import accepts both:

  • Inline: { signature, payload }, with the payload as a normal JSON object. This is the shape the JSON Schema validates.
  • Compact (JWT-style): { signature, payloadBase64 }, with the payload carried as a single base64url string. Import decodes payloadBase64 back to the payload before validating it.

Verification always checks the signature against the base64url payload, so a bundle that was re-serialised (for example, pretty-printed) still verifies as long as the payload’s content is unchanged.

Every payload carries provenance plus exactly one of three bundle bodies, chosen by exportType.

{
"schemaVersion": "1.2.0",
"exportType": "single",
"exportedAt": "2026-08-18T10:00:00.000Z",
"exportedBy": { "email": "admin@example.com", "name": "Admin User" },
"source": { "tenantName": "acme-staging", "platformVersion": "0.4.60" },
"assistant": { "...": "an AssistantExport" }
}
Payload fieldRequiredMeaning
schemaVersionBundle format version, see Versioning.
exportTypesingle, batch, or module.
exportedAtISO 8601 timestamp of the export.
exportedBy{ email, name } of the exporting user.
source{ tenantName, platformVersion }, where the export came from.
assistantwhen singleOne AssistantExport.
assistantswhen batchAn array of AssistantExport.
modulewhen moduleA ModuleExport (name, description, its assistants, and any shared knowledge base / collections).

An AssistantExport groups everything needed to reconstruct one assistant:

  • assistant: the assistant configuration: name, description, systemPrompt, model settings (modelName, modelType, temperature, maxTokens, reasoning options), capabilities (RAG, web search, structured output, function calling, image generation, …), guardrails (content filtering, PII detection, blocked topics), routing keywords, suggestedPrompts, and portability fields (requiredCategory, preferredModel, testedModels, requiredCapabilities).
  • endpoints: the assistant’s API endpoints: name, slug, userPromptTemplate (a Handlebars template), optional parametersSchema / outputSchema, allowedMethods, fileUploadConfig, per-endpoint configuration (timeout, token/temperature overrides, citations), and inputSources, the input-source bindings (e.g. a live url fetch that grounds the prompt) added in schema 1.2.0.
  • knowledgeBaseItems: knowledge base entries as references, not content (see below).
  • collections: the collection names the assistant belongs to.
  • promptSections: optional structured prompt-builder sections (context, persona, instructions, …) with order and enabled state.
  • guardConfigs: optional per-assistant guard-chain overrides (e.g. Presidio PII thresholds and actions).
  • API keys and connection credentials are never exported. Each environment keeps its own, so you mint endpoint keys and bind connections again after import. (See Getting a key.)
  • Knowledge base document content does not travel. A knowledgeBaseItems entry of type document carries only a reference (filename, a SHA-256 contentHash, and fileSizeBytes), not the file’s bytes. Type url entries carry the address and a refreshSchedule. On import the assistant is wired up with these references, but the actual documents are not re-embedded and won’t ground answers until you supply and process them in the target environment. Don’t expect an imported assistant to arrive with a populated, grounded knowledge base.
  • Environment-specific bindings (concrete model deployments, resource names, and other per-tenant infrastructure) do not travel. Portability is by tier instead (next).

A bundle records a model tier (requiredCategory: economy, value, performance, or frontier) rather than pinning a concrete deployment. On import, the target environment’s category map resolves the tier to one of its own approved deployments, so the same bundle runs on different environments without edits. modelName and preferredModel are also carried, but they’re advisory, used for “running on X, tested on Y” hints, not for resolution. A null requiredCategory is allowed and resolves as value at runtime.

The format uses semantic versioning in schemaVersion. The current version is 1.2.0, and the accepted set is enumerated in the schema (1.1.0, 1.2.0). Import validates a bundle against the schema for the version it declares, then applies migrations as needed:

  • Same version: accepted as-is.
  • Older version: migrated forward through a registry of sequential steps (1.0.0 → 1.1.0 → 1.2.0) before validation. For example, 1.1.0 → 1.2.0 is a pure version stamp because the added inputSources field is optional.
  • Newer minor/patch, same major: accepted (forward-compatible); fields the platform doesn’t recognise are ignored.
  • Newer major: rejected with a “please upgrade the platform” message.

Because minor bumps are additive and backward-compatible, older bundles keep validating: 1.2.0 added the optional endpoint inputSources field, and 1.1.0 bundles remain valid.

The signing CLI generates a key pair, signs an unsigned payload JSON into a .ajutant file, and validates an existing bundle against the schema:

Terminal window
# 1. Generate an RSA key pair (private stays secret; register the public key).
npx tsx scripts/sign-export.ts --generate-keys --output-dir ~/.ajutant-keys/
# 2. Sign an unsigned payload into a bundle.
npx tsx scripts/sign-export.ts \
--input my-assistant.json \
--key ~/.ajutant-keys/private.pem \
--output my-assistant.ajutant
# 3. Validate a bundle against the schema (no key needed).
npx tsx scripts/sign-export.ts --input my-assistant.ajutant --validate

The public key must be registered in the target environment for import to trust the bundle, see Signing keys.

  • Authoritative schema: lib/export-import/export-schema-v1.2.0.json
  • Format code: lib/export-import/{schema,types,migration,validator,signature}.ts
  • Signing CLI: scripts/sign-export.ts
  • Related: Exporting and importing assistants, Signing keys