Skip to content

Build a bundle file

Ferrith ChatFor adminsFor integratorsVerified 19 Oct 2026

A workflow, an agent or a grid template moves between workspaces as one zip, a bundle. Export on the workflow or agent page, or on a grid template's row, writes it; the Import button beside it reads it. Inside, nothing identifies the workspace it came from: an agent is named by a key the file invents (agent-1, agent-2), a workflow's steps refer to those keys, and no member, role or record id travels. So the file can be written by hand, or by an AI assistant, as readily as it can be exported.

Everything an import creates arrives as a draft, under the importing person's name, and nothing is used until it is promoted. A drafting library — a document type with its fields, blocks, templates and tests — has its own file, described in Build a library import file.

Before you start

  • Automation plan for workflows and agents (the Workflows and Agents pages); grid templates need Analysis grids.
  • Importing a workflow needs the workflow-author permission, importing an agent the agent-author permission; a grid template can be imported by any member.
  • A bundle is at most 250 MB. Each JSON entry inside it is at most 1 MB, and a bundle holds at most 2,000 entries.

What's in the file

A zip with manifest.json at the top and the entries for one kind of thing beside it. Every JSON file is UTF-8 with camel-case property names.

text
manifest.json                                   the manifest — what the bundle is
workflow/workflow.json                          a workflow bundle: the workflow's name and description
workflow/workflow.yaml                          its steps, in the workflow language
workflow/diagram.json                           the layout of the visual editor (optional)
agents/agent-1/agent.json                       an agent — the workflow's first, or the bundle's only one
agents/agent-1/documents/doc-1/document.json    one of its knowledge documents
agents/agent-1/documents/doc-1/binary           that document's bytes
template/template.json                          a grid-template bundle: the questions and the model

A workflow bundle carries workflow/… and one agents/… folder for every agent its steps use. An agent bundle carries one agents/… folder and nothing under workflow/. A grid-template bundle carries template/template.json alone. Agent keys run agent-1, agent-2, … in the order the file lists them; document keys run doc-1, doc-2, … within each agent.

The manifest

manifest.json says what the bundle is. Nothing in it identifies the workspace the file came from.

Property Value
formatVersion 1 — the file format. An import refuses a file written for a newer format rather than guess.
kind workflow, agent or grid-template.
name The name the import shows — the workflow's, the agent's or the template's.
exportedAtUtc When the file was written, as an ISO date and time in UTC.
entityNames The names of everything the bundle creates — the workflow and its agents, say.
notices Notes shown to the person importing: a reviewer list that was reduced to admins, a knowledge document that was skipped.

A workflow

workflow/workflow.json holds the name and an optional description. workflow/workflow.yaml holds the steps, written exactly as the workflow editor's YAML tab shows them — the workflow reference is the language — with two differences a bundle makes:

  • Every agent step's agentId is the agent's bundle key (agent-1), never an id from a workspace, and its agentVersion is active. The import creates the agents first and rewrites the keys to the new agents' ids.
  • A review step's reviewers list carries only admins and owner. A bundle names no member and no role; a list that would be empty is written as admins, and the manifest says so, so the reviewers are chosen again after the import.

workflow/diagram.json, when present, is the visual editor's layout for the steps; without it the editor lays the workflow out itself.

An import validates the rewritten YAML as the editor would and refuses the whole bundle, creating nothing, when it does not pass.

An agent

agents/<key>/agent.json:

Property Value
key The bundle key — the folder's name, agent-1.
name The agent's name. The import adds a suffix when the workspace already has one by that name.
description Optional.
systemPrompt The system prompt, as the agent editor shows it. Optional.
greetingMessage The first message a conversation with the agent shows. Optional.
usageContext chat for an agent people talk to, workflow for one that works inside workflows. An agent inside a workflow bundle is imported as a workflow agent whatever this says.
documents The knowledge documents, one entry per document, each { key, filename, mimeType }.

No model is named: the workspace assigns its models when the agent is created, as it does for an agent made in the editor.

Each knowledge document has a folder agents/<key>/documents/<docKey>/ holding document.json — the same { key, filename, mimeType } as the agent's list — and binary, the file's bytes under that fixed name. A document's mimeType is one the workspace accepts for an upload: application/pdf, a Word document, Markdown or plain text. The import stores each document and processes it as an upload, within the workspace's document size and page limits; a document that fails them stops the import.

A grid template

template/template.json:

Property Value
name The template's name.
description Optional.
modelId The model the grid's questions run on. When the workspace does not serve it, the import uses the workspace's default and says so in a notice.
columns The questions: each { label, prompt, shape, order }, with shape one of text, yesNo, date or number.

A grid template carries questions and a model, never documents or answers.

What the import does

  1. Reads the manifest, and refuses a file with no manifest, an unknown kind, or a formatVersion newer than the workspace's.
  2. Checks what the bundle would create against the workspace's limits — agents against the agent limits, knowledge documents against the storage limit — before creating anything.
  3. Creates every agent as a draft, then every knowledge document, then the workflow as a draft with its YAML rewritten onto the new agents, or the grid template. A name the workspace already has gets a suffix.
  4. Shows the notices: the exporter's, and its own — renamed items, the reduced reviewer lists, a model it replaced.

Anything that fails after the first write undoes everything the import created, so a workspace never holds half a bundle.

A complete example

A workflow with one agent and no knowledge documents. Save the four files under the names shown, zip them and import the zip on the Workflows page.

manifest.json:

json
{
  "formatVersion": 1,
  "kind": "workflow",
  "name": "Letter review (example)",
  "exportedAtUtc": "2026-10-18T09:00:00Z",
  "entityNames": ["Letter review (example)", "Letter reviewer (example)"],
  "notices": []
}

workflow/workflow.json:

json
{
  "name": "Letter review (example)",
  "description": "Reviews a letter and reports what to change. An example for learning bundles, not for real work."
}

workflow/workflow.yaml:

yaml
name: Letter review (example)
version: '1'
entry: review
steps:
  - id: review
    kind: agent
    agentId: agent-1
    agentVersion: active

agents/agent-1/agent.json:

json
{
  "key": "agent-1",
  "name": "Letter reviewer (example)",
  "description": "Reads a letter and lists what to change before it is sent.",
  "systemPrompt": "You review business letters. List, as numbered points, anything that is unclear, missing or wrongly worded, and suggest the change. Do not rewrite the letter.",
  "greetingMessage": null,
  "usageContext": "workflow",
  "documents": []
}

A grid template is one file beside the manifest (kind: grid-template):

template/template.json:

json
{
  "name": "Lease facts (example)",
  "description": "The facts to pull from every lease in a folder.",
  "modelId": "gpt-oss-120b",
  "columns": [
    { "label": "Tenant", "prompt": "Who is the tenant?", "shape": "text", "order": 0 },
    { "label": "Break clause", "prompt": "Does the lease contain a break clause?", "shape": "yesNo", "order": 1 },
    { "label": "Term ends", "prompt": "On what date does the term end?", "shape": "date", "order": 2 },
    { "label": "Annual rent", "prompt": "What is the annual rent, as a number?", "shape": "number", "order": 3 }
  ]
}

Make the zip

Zip manifest.json and the folders beside it, not a folder that holds them: the import looks for manifest.json at the top of the zip.

  • Windows: select the manifest and the folders, right-click and choose Compress to ZIP file.
  • macOS: select them, Control-click and choose Compress.
  • PowerShell 7, in the folder that holds them: Compress-Archive -Path manifest.json, workflow, agents -DestinationPath letter-review.zip (name the folders the bundle has).
  • Terminal on macOS or Linux, in the same folder: zip -r letter-review.zip manifest.json workflow agents.

If the import is refused

The import says What to do
The file is not a Ferrith bundle, or has no manifest manifest.json is not at the top of the zip, or is not valid JSON.
The bundle was written by a newer version Its formatVersion is above the workspace's; write the file for format 1.
The workspace's agent limit would be exceeded Fewer agents, or a higher limit from your administrator.
The workspace's storage limit would be exceeded Smaller or fewer knowledge documents.
A document was rejected Its mimeType is not accepted, or the file is over the size or page limit.
The workflow is invalid The YAML failed the editor's checks — the message names the step and the rule. Nothing was created.

The schema

One schema per file. Each is published at its $id, so a tool can fetch it; a code editor pointed at the right one checks the file as you type.

json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://docs.ferrith.ai/reference/schemas/bundle-manifest.json",
  "title": "Ferrith bundle: manifest.json",
  "type": "object",
  "required": ["formatVersion", "kind", "name"],
  "additionalProperties": false,
  "properties": {
    "formatVersion": { "const": 1, "description": "The file format version." },
    "kind": { "enum": ["workflow", "agent", "grid-template"], "description": "What the bundle holds." },
    "name": { "type": "string", "minLength": 1, "description": "The name the import shows." },
    "exportedAtUtc": { "type": "string", "format": "date-time", "description": "When the file was written, in UTC." },
    "entityNames": { "type": "array", "items": { "type": "string" }, "description": "The names of everything the bundle creates." },
    "notices": { "type": "array", "items": { "type": "string" }, "description": "Notes shown to the person importing the file." }
  }
}
json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://docs.ferrith.ai/reference/schemas/bundle-workflow.json",
  "title": "Ferrith bundle: workflow/workflow.json",
  "type": "object",
  "required": ["name"],
  "additionalProperties": false,
  "properties": {
    "name": { "type": "string", "minLength": 1, "description": "The workflow's name; suffixed on import when the workspace has one already." },
    "description": { "type": ["string", "null"], "description": "Shown on the workflow's card." }
  }
}
json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://docs.ferrith.ai/reference/schemas/bundle-agent.json",
  "title": "Ferrith bundle: agents/<key>/agent.json",
  "type": "object",
  "required": ["key", "name", "usageContext", "documents"],
  "additionalProperties": false,
  "properties": {
    "key": { "type": "string", "pattern": "^agent-[0-9]+$", "description": "The bundle key — the folder's name, and what a workflow step's agentId names." },
    "name": { "type": "string", "minLength": 1 },
    "description": { "type": ["string", "null"] },
    "systemPrompt": { "type": ["string", "null"], "description": "The system prompt as the agent editor shows it." },
    "greetingMessage": { "type": ["string", "null"], "description": "The first message a conversation with the agent shows." },
    "usageContext": { "enum": ["chat", "workflow"], "description": "An agent people talk to, or one that works inside workflows." },
    "documents": {
      "type": "array",
      "description": "The knowledge documents; each has a folder beside this file holding document.json and binary.",
      "items": { "$ref": "https://docs.ferrith.ai/reference/schemas/bundle-document.json" }
    }
  }
}
json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://docs.ferrith.ai/reference/schemas/bundle-document.json",
  "title": "Ferrith bundle: agents/<key>/documents/<docKey>/document.json",
  "type": "object",
  "required": ["key", "filename", "mimeType"],
  "additionalProperties": false,
  "properties": {
    "key": { "type": "string", "pattern": "^doc-[0-9]+$", "description": "The document's key — its folder's name." },
    "filename": { "type": "string", "minLength": 1, "description": "The name the document shows in the workspace." },
    "mimeType": { "type": "string", "minLength": 1, "description": "The file's type, as an upload declares it: application/pdf, a Word document, Markdown or plain text." }
  }
}
json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://docs.ferrith.ai/reference/schemas/bundle-grid-template.json",
  "title": "Ferrith bundle: template/template.json",
  "type": "object",
  "required": ["name", "columns"],
  "additionalProperties": false,
  "properties": {
    "name": { "type": "string", "minLength": 1 },
    "description": { "type": ["string", "null"] },
    "modelId": { "type": ["string", "null"], "description": "The model the questions run on; the workspace's default when it is not served." },
    "columns": {
      "type": "array",
      "minItems": 1,
      "items": {
        "type": "object",
        "required": ["label", "prompt", "shape", "order"],
        "additionalProperties": false,
        "properties": {
          "label": { "type": "string", "minLength": 1, "description": "The column heading." },
          "prompt": { "type": "string", "minLength": 1, "description": "The question asked of every document." },
          "shape": { "enum": ["text", "yesNo", "date", "number"], "description": "The kind of answer." },
          "order": { "type": "integer", "minimum": 0, "description": "The column's position, from 0." }
        }
      }
    }
  }
}