Packaging a Custom Integration in a Pack
Ship a custom integration inside a Scrydon Pack so pack sources deliver it automatically via Git/OCI sync
A pack can carry one or more custom integrations using the integration content kind. Once a pack is installed the custom integration appears in Settings → Platform → Integrations → Custom — no separate upload step required. Combine this with pack sources (Git/OCI sync) for a fully automated delivery pipeline: push to Git, Scrydon picks up the pack, the integration lands in the catalog. For how versions propagate from release to runtime (pins, update policies, breaking-change review), see Pack versioning.
This page is about packaging an already-built integration artifact inside a pack. To build the integration artifact itself, see the Integration authoring guide.
When to use this
Use an integration pack content kind when:
- You want to deliver a custom integration from a Git repository or OCI registry without a manual upload for each release.
- A pack you ship requires a custom vendor capability (LLM, STT, tools, blocks) and you want the integration and the ontology/workflows/process-flow to install together.
- You want CI/CD to control the integration version and have Scrydon pick it up automatically on each release.
Use the Manual upload door when the integration artifact exceeds 8 MB (the pack delivery cap) or when you have no pack source set up and want a one-off install.
Prerequisites
- A built integration artifact —
bunx @scrydon/sdk-authoring integrations buildproducesdist/{vendorId}-{version}.bundle.tar.gz. Rename it tobundle.tar.gzfor the pack subdir. - An existing pack project with
pack.json. If you don't have one, create it:bunx @scrydon/sdk-authoring pack init my-pack --outDir ./packs.
Subdir layout
For each integration you want to include, create a subdirectory (conventionally named integration-<vendorId>) containing exactly two files:
packs/my-pack/
├── pack.json
├── ontology/ # optional — other kinds can coexist
│ └── manifest.json
└── integration-acme-crm/ # one subdir per integration
├── manifest.json
└── bundle.tar.gzintegration-acme-crm/manifest.json
{
"vendorId": "acme-crm",
"version": "1.2.0"
}| Field | Rules |
|---|---|
vendorId | Lowercase slug: ^[a-z0-9][a-z0-9-]*$, 1–100 characters. Must match the id declared inside the integration artifact. |
version | Semver string (e.g. 1.2.0, 2.0.0-beta.1). Must match the version declared inside the artifact. |
The bundler (scrydon-pack build) validates this manifest and confirms bundle.tar.gz is present and within the 8 MB cap. The inner manifest is re-validated by the platform at install time.
bundle.tar.gz
The built integration artifact — identical to the file the Manual upload door accepts. Maximum size: 8 MB per artifact when delivered via a pack. Artifacts larger than 8 MB must be uploaded via the Manual upload door (50 MB cap).
Declaring the entry in pack.json
Add the integration entry to contents[] and include "integration" in installOrder:
{
"manifestVersion": 1,
"package": {
"id": "acme-crm-pack",
"name": "Acme CRM Pack",
"version": "1.0.0"
},
"installOrder": ["ontology", "integration"],
"contents": [
{
"kind": "ontology",
"path": "ontology",
"version": "1.0.0",
"required": true
},
{
"kind": "integration",
"path": "integration-acme-crm",
"version": "1.2.0",
"required": true
}
]
}"integration" is a repeatable kind — a single pack can include multiple integration entries, each in its own subdir with its own manifest.json and bundle.tar.gz. Set "required": false for integrations that are optional add-ons.
Build and publish
# Validate and build the .scrydon-pack.tar.gz
cd packs/my-pack
bunx @scrydon/sdk-authoring pack build .
# The output is: dist/acme-crm-pack-1.0.0.scrydon-pack.tar.gzThe bundler validates:
integration-acme-crm/manifest.jsonparses againstIntegrationContentManifestSchema.integration-acme-crm/bundle.tar.gzexists and is ≤ 8 MB.vendorIdandversionin the subdir manifest are consistent with the entry inpack.json.
Publish via pack sources (recommended for CI) or manual upload via Settings → Platform → Packs.
Activation policy
When a pack containing an integration entry is installed, the integration's initial status depends on how the install was triggered:
| Install trigger | Activation status |
|---|---|
| Explicit admin action (marketplace Add/Update, catalog Install) | Active — the admin's install action is the review |
Non-interactive install from a cosign-keyless / cosign-key source | Active — cosign verification is the trust gate |
| Non-interactive install of an unsigned artifact | Pending Review — org admin must approve before it is available |
Today every install path is an explicit admin action, so integrations activate immediately; the Pending Review rows apply to future automated flows (e.g. auto-update on source sync).
Pending Review integrations appear in Settings → Platform → Integrations → Custom with an Activate button. Once activated they are available in the workflow editor.
For non-interactive installs the source's signature policy is the trust gate. Cosign-signed sources are already verified by the reconciler at sync time, so the policy field is trustworthy. See Signature policies for how to set up cosign-keyless signing for your pack source.
Install flow (what happens on packs/install)
The platform reads the catalog row for this pack, which contains the inspected content entries — for integration entries: the vendor identity, the stored artifact reference, and the artifact's sha256 fingerprint.
The platform looks up the pack source's signaturePolicy to determine sourceSigned. Sourceless rows (manual uploads) are treated as unsigned.
The platform checks the organization's integration governance policy. If vendorId is in blockedVendorIds, the entry fails with a structured vendor_blocked error. Other entries in the pack continue to install.
The platform loads the artifact bytes from object storage, recomputes the sha256 hash, and compares it to the fingerprint recorded at catalog time. A mismatch fails the entry with integration_artifact_hash_mismatch.
The platform inspects the artifact (the same validation engine as manual upload) to extract the inner manifest. The vendor identity (vendorId, version) is compared to the catalog entry. On match, the custom integration is registered with the policy-derived status.
Re-installing the same pack with the same artifact hash is a no-op for the integration entry — the existing row is returned as-is. A changed hash updates the row and re-derives the status.
Governance and blocked vendors
If your organization has set blockedVendorIds in the integration governance policy, any pack install that includes a blocked vendor fails that entry with a structured error. The error is surfaced in the pack install result; other entries (ontology, process-flow, etc.) are not affected.
To unblock a vendor, remove it from blockedVendorIds in Settings → Platform → Integrations → Governance, then re-run the pack install.
Troubleshooting
integration_validation_failed during pack inspect
The integration-<slug>/manifest.json failed schema validation.
- Check that
vendorIdis a lowercase slug (^[a-z0-9][a-z0-9-]*$). - Check that
versionis a valid semver (1.2.0, notv1.2.0orlatest). - Run
bunx @scrydon/sdk-authoring pack validate .locally for detailed errors.
integration_artifact_missing during pack install
The catalog row has no stored artifact reference — the artifact was not persisted when the pack entered the catalog. This happens for catalog rows created before the platform supported integration pack entries. Re-sync the source (click Sync now) to re-write the catalog row with the artifact.
vendor_blocked during pack install
The organization's blockedVendorIds policy includes this vendorId. Remove the vendor from the block-list or rebuild the pack without that integration entry.
integration_identity_mismatch during pack install
The vendorId or version inside the artifact's own manifest disagrees with what the pack's subdir manifest.json declared. Rebuild the integration with matching identity fields.
Pack inspect rejects bundle.tar.gz as disallowed_extension
This means bundle.tar.gz is not inside a declared kind: "integration" subdir. Ensure the pack's contents[] has an entry with "kind": "integration" and a path that matches the subdir containing the file.