Delivering & Managing Custom Extensions
Deliver custom extensions through extensions (Git/OCI sources), and manage installed extensions
Manual tarball upload has been removed. Custom extensions are now delivered exclusively
through extensions — a Git or OCI source your CI/CD publishes to, installed from your org's extension
catalog. Publish your .archive.tar.gz inside an extension instead of uploading it directly. See
Extension sources and
Packaging a custom extension in an extension.
Custom Extension Scopes
Extension identities reserve three scopes. Organization uploads are the currently supported custom-extension path:
| Scope | Origin | Visibility | How it's delivered |
|---|---|---|---|
First-party (org:scrydon:) | Baked into the Docker image | All organizations | Shipped with Scrydon releases |
Platform (org:platform:) | Reserved for a platform-scoped extension | All organizations | Not currently enabled; requires dedicated platform-owned artifact storage |
Organization (org:{orgId}:) | Org extension source | Single organization | Org admin installs from the org's extension catalog |
Every organization-installed tool has a platform microVM floor. Only exact reviewed code shipped by Scrydon may use the approved Worker Thread tool backend; a matching vendor name never grants uploaded code that trust. A custom tool is selectable by an isolated Agent only while the exact Runtime Plane backend is qualified and attested.
Delivering via an extension source
Publish your built .archive.tar.gz as an extension content entry inside an extension, to a Git
or OCI source your CI/CD controls. See
Packaging a custom extension in an extension.
Go to Settings > Platform > Extensions → the Sources tab (direct URL:
/settings/platform/extensions#sources) and register your extension source. Sources sync extensions
into your org's extension catalog. See Extension sources.
Synced custom extensions appear in Add extension as rows that name their extension (extension name and version) with a Connect action when the first step after install is a credential, and Install when it is not — either one installs and activates it immediately (your explicit install is the review). A cosign-signed source is also trusted for non-interactive installs; unsigned automated syncs land as Pending Review.
When a new artifact is published, the card shows an Update badge; clicking it takes you to the extension's sheet (Settings → Platform → Extensions → Installed → open the extension → Version & security), where you review workflow impact and apply the update. See Extension version management.
What Happens on Install
When an extension carrying an extension entry is installed, the platform runs the same archive
pipeline it always has:
Extracts the .tar.gz with security checks:
- No symlinks allowed
- No absolute paths
- No path traversal (
../) - No device, FIFO, duplicate-path, or unsupported archive entries
- A bounded entry count and bounded decompression output
- Only allowed file extensions (
.js,.js.map,.json,.svg,.png,.jpg) - A required
dist/index.jsentry point - Maximum extracted size: 100 MB
Validates manifest.json against the ManifestSchema (Zod):
manifestVersionmust be1, the sole supported contract; any other version is rejected- Product workflow UI belongs under
workflow; resources underresources; app consent undersystemMode.permissions - Vendor ID format:
/^[a-z][a-z0-9-]*$/ - Tool IDs reference valid vendor prefixes
- Auth credential types are valid
- No duplicate product IDs
- Required fields are present
Writes the artifact to blob storage:
00_organization/extension-archives/{extensionId}/{version}/archive.tar.gzRegisters the extension with the vendor ID, version, manifest JSON, storage key, SHA-256
hash, and scope in the org's installed-extension record (extension_code_release).
There is no compatibility upgrade during install. Rebuild any archive built against an older
contract with the current authoring SDK before publishing it. Do not hand-edit manifest.json inside the archive.
Managing Installed Custom Extensions
Settings > Platform > Extensions > Installed lists every extension your organization has explicitly installed — through Add extension or an extension-catalog install — plus the built-in Scrydon vendor (shipped with every organization, cannot be removed) and your registered MCP servers, one row each. Catalog vendors nobody has added are not rows; they live in the Add-extension flow. A row reads as a sentence: the vendor's mark and name, its publisher (Scrydon, a vendor, or your org), one line of what it brings in words, a single line of state, and its version — an available update shows in the version column as 1.3.1 → 1.4.2 so it never displaces a state that needs a human. Enabling happens inside the sheet, where its org-wide scope is spelled out. Rows that need attention sort to the top, and the Filter button narrows by what a row includes (AI models, agent tools, content, MCP servers), state, and publisher.
States
| State | Meaning |
|---|---|
| Available | Ready to use — credentials in place, products available in workflows. Content extensions append adoption: · on in 3 of 5 workspaces, or · not on in any workspace yet when no workspace has turned anything on |
| Needs credentials · step 2 of 3 | Installed, but an org-owned setup step is unfinished. The state names the next step and, in grey, where it sits in the count — the same count the sheet's Setup checklist shows. There is no separate "in progress" state |
| Credential failing | The last health check against the vendor failed — reconfigure or reconnect |
| Source sync failing | The extension source that delivers this extension cannot sync — see the Sources tab |
| Deprecated until … | Scheduled for removal on that date; workflows still run until then |
| Not enabled | Every product is switched off — the extension stays listed so you can switch it back on |
| Disabled | Disabled by lifecycle policy |
| Awaiting approval / Unreachable | MCP servers only: pending review, or the last discovery could not reach the server. On a content extension whose next step is an MCP approval, the state reads Awaiting approval · step 2 of 2 |
The extension sheet
Click a row to open the extension's sheet. Everything about it lives there, in tabs:
- Setup — while something is still owed, your org-owned steps as a checklist ("Microsoft account connected — OAuth · 12 products") with a progress meter, and the connection sections (console instructions, the OAuth redirect URL, organization-wide access) laid out flat beneath it. Once nothing is owed the checklist goes and the tab reads as the connection overview — the state it would have reported is already on the extension's row in the list. An extension that needs no account at all keeps a single line saying so.
- Includes — what the extension gives you: AI models first (with the model list and the allowlist policy editor behind each capability), then agent tools grouped by product, each with its Enabled · org-wide switch — Not enabled when it is off. Turn on is the workspace word and never appears here. Every product gets a row, however many there are; the section header states the scale ("142 tools across 18 products") before the list starts.
- Connections — the vendor's connections per product, their environments, and the default execution identity of each connection.
- Access — which workspaces may use the vendor.
- Usage — workflows and knowledge bases that reference it.
- Version & security — present for every extension, and read top to bottom. Provenance first: who published it, whether its code runs in the platform process or in an isolated microVM (a trust shield marks third-party code; a Scrydon-published extension carries none), and May reach — the destinations its tools declare, and whether your organization's egress policy treats that declaration as a grant. Then Updates: the installed version and the one available, what changed, the update policy, and which workflows use it. An extension that ships with the platform has no separate version to check or policy to set, so that half reads as a single line and the provenance half is the tab.
The ··· menu in the sheet's header holds Enable / Disable for the organization and the
lifecycle actions — Review new version, Deprecate / Cancel deprecation, Remove.
The sheet is deep-linkable: /settings/platform/extensions?extension=<slug> opens it directly
(older ?vendor= links still resolve).
The Models & defaults tab
Settings > Platform > Extensions > Models & defaults is the inventory of AI capabilities (LLM, embeddings, speech to text, text to speech, image, video, OCR, moderation, web search) and where the organization's defaults are set: one row per capability, always all of them, with the vendor that serves it and the model it currently resolves to, tagged automatic (the platform ranks what is installed) or pinned. The model name never repeats the vendor beside it. Change opens the picker in place: Select automatically, or a specific provider/model, saved per row. A capability whose vendor is present but resolves no model reads No model resolved rather than a dash. A capability nobody serves offers Add a vendor (the Add-extension flow). A note beside the row names what is worth knowing: a vendor that is installed but has every product switched off (with an Enable shortcut), or a vendor that is being retired and by when. One line heads the tab — Configured capabilities · 6 of 9 · Video, Moderation and Web search have no vendor.
The former Platform Defaults page is this tab; its address
(/settings/platform/defaults) redirects here. Mute the sidebar badge in the tab's footer
acknowledges the current gaps organization-wide so the badge on the Extensions menu item
stays quiet until a new gap appears — the inventory itself is never hidden.
Enable / Disable
Enable / Disable for the organization in the sheet's ··· menu enables or disables every product of the vendor at once; the switch on a product row in the sheet's Includes tab toggles that product alone, org-wide. Disabling removes the products from the workflow editor but does not delete the stored files or credentials.
Versioning
Publish a new extension version to update the extension. The platform stores each version by
{extensionId}/{version}/, so multiple versions can coexist; the live version is the semver-max
active one (or the pinned version if your org pinned one — see
Extension version management).
Uninstalling or superseding a custom extension will break any workflows that reference its tools. Verify no workflows depend on it first.
Catalog and Execution Sequence
When the platform starts:
- Scan first-party archives — parse
manifest.jsonfrom/app/archives/(Docker image) - Scan platform archives — parse manifests from platform storage
- Scan org archives — parse manifests from each organization's storage
- Populate Manifest Catalog — metadata only, no vendor code loaded
- Publish dynamic readiness — the Runtime Plane reports whether the exact digest-pinned vendor profile has current candidate and deployment proof
- Admit on demand — the compiler exposes a custom Agent tool only when its verified SHA-256 source and exact backend pass the security floor
- Execute once — the trusted broker revalidates tenant, environment, connection, source, policy, and binding revisions, then starts a fresh
vendor_archivemicroVM
Custom extension code is never loaded on the Agentic/API host at boot. The platform reads
manifest metadata only. At execution, the active archive bytes are re-hashed, transferred through
a one-use sealed grant, unpacked inside the vendor guest, and deleted with that single-use VM.
The Agent's own agent_turn VM never receives the archive or reusable credentials.
Agent tool readiness
For an isolated Agent, a non-Scrydon tool is available only when all of these are true:
- the active archive has a verified SHA-256 identity and still matches the compiled source revision;
- the authenticated Runtime Plane supports the
vendor_archivecontract; - runner, broker, and enforcer images are digest-pinned;
- a schedulable Kata/KVM substrate is observed;
- signed exact-candidate and deployment proofs match the configured images, profile, and evidence digests;
- actual-request egress, outbound DLP, and audit are ready; and
- the encrypted one-use grant store is ready.
Any missing condition produces a typed remediation error. The platform never degrades an organization-installed Agent tool to a Worker Thread.
Organization Policies
Organization policies (configured by admins) govern custom extension behavior:
Execution Policy
{
"maxConcurrentExecutions": 10,
"maxTimeoutSeconds": 30,
"allowedCapabilities": ["llm", "stt", "tools"]
}Governance Policy
{
"allowedVendorScopes": ["scrydon", "platform"],
"blockedVendorIds": []
}blockedVendorIds— vendor IDs blocked from installing (enforced on extension install)allowedCapabilities— which capability types are permitted
The legacy allowOrgUploads / requireApprovalForUploads fields governed the removed manual
upload flow and no longer have any effect. Whether an extension-delivered extension activates on
install is governed by source signing and the explicit-install policy (see the delivery steps
above), not these fields.
Package Format
Every custom extension is packaged as a .tar.gz archive with this structure:
{extensionId}-{version}.archive.tar.gz
├── manifest.json (vendor metadata, JSON Schemas, UI definitions)
├── dist/
│ └── index.js (compiled, minified ESM; all dependencies inlined)
├── meta/ (automatically generated)
│ ├── sbom.cdx.json (CycloneDX 1.6 SBOM)
│ └── metafile.json (esbuild dependency graph)
└── assets/ (optional)
└── icon.svg, icon.png (vendor/product icons)SBOM Generation
Every custom extension artifact automatically includes a CycloneDX 1.6 Software Bill of Materials (SBOM) at meta/sbom.cdx.json. The SBOM is generated during sdk-authoring extension build with no additional tools or configuration required.
What's in the SBOM
The SBOM lists every NPM package that esbuild archived into dist/index.js:
- Package name and version — exactly what's in the archive
- License — SPDX license identifier from
package.json - Package URL (purl) — machine-readable identifier (
pkg:npm/zod@3.24.0) - Evidence — file paths proving the package is actually archived (tree-shaken code is excluded)
The SBOM only includes packages that esbuild actually archived. If a dependency was tree-shaken away, it won't appear — making the SBOM an accurate reflection of what ships in your archive.
How it's generated
During sdk-authoring extension build, the CLI:
- Runs esbuild with
metafile: trueto capture all input files - Extracts unique NPM packages from
node_modules/paths in the metafile - Reads each package's
package.jsonfor name, version, license, and description - Serializes everything to CycloneDX 1.6 JSON
- Writes the result to
meta/sbom.cdx.jsonalongside the esbuild metafile
No external tools or dependencies are required — the generation is built into the CLI.
How admins use it
After installing a custom extension, the Version & security tab of the extension's sheet shows the SBOM component count and lists all auto-detected packages alongside declared dependencies. See Custom Extension Review Before Activation for the full review workflow.
Compliance
The CycloneDX format is widely supported by compliance tools. For environments that require SPDX, convert using:
cyclonedx-cli convert --input-file meta/sbom.cdx.json --output-file sbom.spdx.json --output-format spdxjsonThe raw esbuild dependency graph is also available at meta/metafile.json for debugging. You can visualize it at esbuild.github.io/analyze.
Custom Extension Review Before Activation
After a custom extension is installed, administrators can review its contents before enabling it for the organization. The extension's sheet provides a structured review experience across its tabs.
Includes
Shows all products, tools, triggers, and runtime capabilities the custom extension provides, each with its Enabled · org-wide switch. Intelligence capabilities (LLM, STT, TTS, Embedding) display model counts and support allowlist-mode policy configuration. See Capabilities for details.
Version & security — dependencies
The dependency facts sit in Version & security, in two complementary views of the custom extension's dependency tree:
- Declared dependencies — Hand-authored entries from
defineToolkit({ dependencies: [...] })that include a human-readablereasonfor each dependency. - Auto-detected dependencies (SBOM) — The full set of NPM packages that esbuild archived into
dist/index.js, extracted frommeta/sbom.cdx.json. Each entry shows the package name, version, license, and a machine-readable package URL (purl).
The section shows the total SBOM component count so administrators can quickly gauge the custom extension's dependency footprint. Organization dependency policies (risk levels, blocklists, post-install script blocking) are evaluated against both declared and auto-detected dependencies.
If a dependency is blocked by the organization's policy, it is flagged in Version & security with the reason (e.g., "Risk 'high' exceeds max 'medium'" or "Has post-install scripts"). Blocked dependencies prevent the custom extension from being activated until the policy is adjusted or the .archive.tar.gz is rebuilt without the offending package.
Setup
Shows the vendor's authentication configuration (OAuth, API Key, Bot Token, or None) and allows administrators to configure credentials before enabling the vendor's capabilities.
Storage Layout
First-party (baked into Docker image):
/app/archives/{extensionId}/dist/index.js
Platform uploads:
{platformPrefix}/extension-archives/{extensionId}/{version}/archive.tar.gz
Org uploads:
{storageKeyPrefix}/00_organization/extension-archives/{extensionId}/{version}/archive.tar.gzTroubleshooting
Install fails with "Invalid manifest"
Run the CLI validator locally before publishing the extension to see detailed errors:
bunx @scrydon/sdk-authoring extension test --level staticCommon issues:
- Vendor ID contains uppercase or special characters
- Tool ID doesn't start with the vendor ID prefix
- Missing required fields (
id,name,version,icon,auth) - Zod schema uses unsupported types (the extractor converts Zod → JSON Schema)
Custom extension status shows "Failed"
Check the error message in the extension's sheet (and server logs). Common causes:
dist/index.jshas a syntax error- Default export is not a
defineExtension()result - Missing
export defaultstatement - Runtime import fails (ensure all deps are archived — esbuild should inline them)
Tool doesn't appear in the workflow editor
- Verify the extension that carries the extension is installed (the extension's row on Settings > Platform > Extensions > Installed) and its version is active
- Verify the product is enabled for your organization in Settings > Platform > Extensions
- Check the block's
category— it must be"tools","blocks", or"triggers"to appear in the correct palette section
Build warns that block tool configuration will be omitted
tools.config.tool and tools.config.params are functions, so they cannot be represented in manifest.json. Replace dynamic routing with tools.toolSelector, expose direct parameters as block inputs, and move any remaining parameter coercion into the tool's execute() function. Do not ignore this warning: an installed workflow block only has the JSON-safe manifest definition at serialization time.