https://schema.openenvelope.org/team/v1.jsonListed on SchemaStore — VS Code, JetBrains, and any SchemaStore-aware editor will validate *.envelope.json files automatically, no configuration needed.
Envelope Open Schema Spec
The open standard for composable AI agent team definitions — v1
The idea
The Envelope team definition — the structure that describes agents, roles, hierarchy, escalation paths, required secrets, and adapters — is published as a formal, versioned open source specification. Anyone can read it, validate against it, and build tooling around it. The marketplace, billing, deployment infrastructure, and matching remain proprietary.
This mirrors the Elastic model: open source the engine and specification (Elasticsearch), keep the managed distribution proprietary (Elastic Cloud). The open schema drives adoption and ecosystem; Envelope is the canonical place to publish and deploy.
The strategic bet: declarative beats imperative
The AI agent market currently looks code-first. LangGraph, CrewAI, Vertex AI ADK — the dominant platforms all ask developers to write Python or JavaScript to define their agents. This feels like the natural state because developers are the early adopters and code gives maximum flexibility.
But infrastructure always moves from imperative to declarative. You used to provision servers by SSHing in and running commands. Then came Terraform — describe what you want, the runtime figures out how. You used to deploy containers by writing shell scripts. Then came Kubernetes manifests. The imperative approach wins early because it's expressive. The declarative approach wins at scale because it's portable, auditable, versionable, and toolable by anyone — not just the person who wrote the original code.
multi-agent teams are on the same trajectory. Right now they're application code. Over time they become infrastructure — repeatable, deployable, governed, versioned. When that shift happens, the platform that owns the declarative standard owns the ecosystem.
The Envelope schema is that bet. Not a proprietary format that locks builders in — an open standard that any runtime can implement. The same way OpenAPI became how APIs describe themselves regardless of which framework serves them, the Envelope Team Definition Schema becomes how multi-agent teams describe themselves regardless of which platform runs them.
Why Envelope should not build code generation as a core product: Code generation — taking an Envelope team definition and emitting Python for CrewAI or LangGraph — is fighting on the wrong turf. Python developers who want CrewAI will use CrewAI. Envelope adding a code gen layer doesn't add enough to win that fight, and it positions Envelope as a wrapper around other frameworks rather than the standard those frameworks implement. Every code-gen integration built is a stopgap that becomes irrelevant the moment the target platform adopts the schema natively.
The right interim move: For builders who need to hand off to a Python developer — generate the code, offer a copy button or a one-click GitHub export. This is a collaboration handoff, not a developer tool. It helps non-technical Envelope builders get their team definition into a Python developer's hands. It is explicitly not a path to competing with Python IDEs or framework-native tooling.
The long-term conclusion: Code-first platforms that want to be where multi-agent teams are deployed will implement the Envelope schema natively — because that's where the teams are published, where builders have reputation, and where deployers go to find them. Envelope doesn't need to speak Python. It needs to make the schema compelling enough that Python frameworks speak Envelope.
Why open source the schema
It makes the format a standard, not a proprietary format. Once other tools, runtimes, and frameworks can read and write Envelope team definitions, builders are working inside an ecosystem rather than a walled garden. Adoption friction drops significantly.
It creates community leverage without giving away the business. The community builds validators, linters, IDE extensions, visual editors, and converters. Envelope benefits from all of it without building any of it.
It is a long-term moat. If the Envelope schema becomes the standard way to define an multi-agent team — the way OpenAPI became the standard for describing APIs — then Envelope as the canonical marketplace for that schema is very hard to displace. Competitors either adopt the standard (which benefits Envelope) or start from scratch (which is expensive for them).
It unlocks enterprise adoption. Enterprises can store and audit team definitions in their own Git repos, validate them in their own CI/CD pipelines, and publish to Envelope when ready. That level of control is often a prerequisite for enterprise procurement.
How builders use it
In the editor
A builder references the schema at the top of their team definition file. Their IDE validates instantly — required fields flagged, agent properties autocompleted, invalid adapter names underlined — without touching Envelope.
{
"$schema": "https://schema.openenvelope.org/team/v1.json",
"name": "Support Tier",
"slug": "support-tier",
"version": "1.0.0",
"agents": [...]
}
Local validation
npm install @openenvelope/schema
import { validate } from '@openenvelope/schema';
import { readFileSync } from 'fs';
const team = JSON.parse(readFileSync('team.json', 'utf-8'));
const result = validate(team);
if (!result.valid) {
console.error(result.errors);
process.exit(1);
}
console.log('Valid');
In CI/CD
Every pull request validates the team definition before it reaches the registry. Invalid definitions never get published.
# .github/workflows/validate.yml
- name: Validate team definition
run: |
node -e "
const { validate } = require('@openenvelope/schema');
const team = require('./team.json');
const r = validate(team);
if (!r.valid) { console.error(r.errors); process.exit(1); }
console.log('Valid');
"
Version controlled
Team definitions live in Git alongside the code they support. Schema versioning means every definition is traceable to the spec version it was built against.
Schema reference
1. Team definition (top-level)
The root object of every .envelope.json file.
{
"$schema": "https://schema.openenvelope.org/team/v1.json",
"name": "Support Tier",
"slug": "support-tier",
"version": "1.2.0",
"description": "A three-tier support team that triages, escalates, and resolves customer tickets.",
"category": "customer-support",
"tags": ["zendesk", "slack", "triage"],
"visibility": "public",
"pricing": {
"model": "per_run",
"amount": 0.05,
"currency": "usd"
},
"inputs": {
"ticket_text": {
"type": "string",
"description": "The raw ticket content to be triaged.",
"required": true
},
"priority_override": {
"type": "string",
"enum": ["low", "normal", "high", "urgent"],
"description": "Optional manual priority. If omitted, the team determines priority.",
"required": false
}
},
"outputs": {
"resolution": {
"type": "string",
"description": "The final response or action taken."
},
"assigned_agent": {
"type": "string",
"description": "The key of the agent that handled the ticket."
},
"escalated": {
"type": "boolean",
"description": "Whether the ticket was escalated."
}
},
"requiredSecrets": ["ZENDESK_API_KEY", "SLACK_BOT_TOKEN"],
"requiredVariables": ["SUPPORT_EMAIL"],
"agents": [...]
}
| Field | Type | Required | Description |
|---|---|---|---|
$schema | string | Yes | Always https://schema.openenvelope.org/team/v1.json |
name | string | Yes | Human-readable display name |
slug | string | Yes | URL-safe identifier, unique within the registry. Lowercase letters, numbers, and hyphens only ([a-z0-9-]+). |
version | string | Yes | Semver string (MAJOR.MINOR.PATCH) |
description | string | Yes | Short description shown in the registry (max 300 chars) |
category | string | No | Registry category slug (see categories reference) |
visibility | string | Yes | "public" — listed in registry. "team" — accessible to members of the builder's org only (not publicly discoverable). "private" — accessible only to the publishing API key. |
pricing | object | No | Omit for free teams. When omitted, the team is free and pricing.model defaults to "free". |
pricing.model | string | Yes | "free", "per_run", or "per_k_tokens" (see pricing models). "subscription" is planned for a future release. |
pricing.amount | number | Yes | Price in the specified currency |
pricing.currency | string | No | ISO 4217 code. Defaults to "usd" |
forkable | boolean | No | Whether other builders may fork this team. Defaults to true for public teams. Set to false to prevent forking while keeping the team public. |
forkedFrom | object | No | Present when this team was derived from another (see forking) |
timeout | object | No | Default timeout configuration for all agents in the team. Per-agent modelConfig.timeoutMs overrides this. |
timeout.runMs | number | No | Maximum wall-clock time in ms for the entire run. Default: 30000. Max enforced by runtime. |
timeout.agentMs | number | No | Maximum time in ms for a single agent invocation. Default: 30000. |
readme | string | No | Long-form Markdown documentation shown on the team's registry page. No hard limit, but keep under 10,000 chars for readability. |
icon | string | No | URL of a square PNG or SVG icon (min 256×256px) shown in the registry. Must be publicly accessible. |
inputs | object | No | Named input fields the team accepts. Keys are field names. Accepted by the registry but schema validation is not enforced in v1 — planned for v2. |
outputs | object | No | Named output fields the team returns. Keys are field names. Accepted by the registry but output schema validation is not enforced in v1 — planned for v2. |
requiredSecrets | string[] | No | Secret names the deployer must supply at install time. Values are never in the schema. |
requiredVariables | string[] | No | Non-secret variable names the deployer must supply at install time (e.g. ["COMPANY_NAME", "SUPPORT_EMAIL"]). Human-readable descriptions live in the registry UI, not in the schema file. |
tags | string[] | No | Free-form tags for search. Currently accepted by the registry but not yet indexed for filtering. Planned for registry v2. |
changelog | string | No | Human-readable description of what changed in this version. Shown in the registry UI and returned by the versions API. Best practice: include this on every publish so deployers can understand what changed without reading the diff. |
agents | object[] | Yes | One or more agent definitions (see below) |
metadata | object | No | Team-level metadata. Preserved by the registry and returned in API responses. Envelope populates metadata.generatedBy automatically on export. |
metadata.generatedBy | string | No | Attribution string. Canonical value when exported by Envelope: "Envelope · openenvelope.org". |
workspace | object[] | No | Workspace documents declared by this team. Each entry persists between agent runs and is scoped to the install. See workspace block reference below. |
Workspace block
The workspace array declares persistent documents that accumulate state across agent runs. Each entry is a separate document scoped to the install.
{
"workspace": [
{
"name": "outreach-list",
"type": "contact-list",
"columns": [
{ "name": "company", "owner": "human", "id": true },
{ "name": "email", "owner": "agent", "type": "string" },
{ "name": "email-status", "owner": "both", "type": "string" },
{ "name": "sent-at", "owner": "agent", "type": "string", "pii": false }
],
"statusValues": ["unverified", "verified", "needs-lookup", "sent", "replied", "bounced"],
"triggers": [
{ "column": "email-status", "status": "unverified", "action": "run" }
],
"hypotheses": ["Verified contacts will have a higher reply rate."]
}
]
}
Workspace document fields:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Unique document name within the install. Lowercase letters, numbers, and hyphens only ([a-z0-9-]+), up to 60 characters. Used to reference the document in agent tool calls and condition triggers. |
type | string | No | Document type hint for UI rendering and schema bootstrapping. Not validated by the runtime. Examples: "contact-list", "content-queue", "task-backlog". |
columns | object[] | No | Declared columns with ownership. Agents may only write to columns they own; humans may edit any column. |
columns[].name | string | Yes | Column name. Used as a key in row data objects. |
columns[].owner | string | Yes | "agent" — agent writes this column; humans may read and edit. "human" — human writes only; the agent reads but never overwrites. "both" — either may write, with conflict detection. |
columns[].type | string | No | Value type hint: "string" (default), "number", or "boolean". Used for rendering and export formatting. |
columns[].id | boolean | No | If true, this column is the unique identifier for row addressing. Only one column per document should have id: true. |
columns[].pii | boolean | No | Marks the column as containing personally identifiable information. Enables appropriate handling in exports and API responses and hard-delete handling for erasure requests. |
statusValues | string[] | No | Valid values for status-type columns. Enforced at runtime — agent writes with an undeclared status value are rejected. Required for triggers to work correctly. |
triggers | object[] | No | Condition triggers evaluated against the document's status index. Fire when any unblocked row has a matching column/status combination. |
triggers[].column | string | Yes | The declared column to evaluate. |
triggers[].status | string | Yes | The status value that causes the trigger to fire. Must be a declared statusValue. |
triggers[].action | string | No | "run" (default) — fires the full team. "notify" — sends a notification without running. |
hypotheses | string[] | No | Forward-compatibility field for predicted outcomes. Ignored by the runtime in v1.2.0. |
Built-in row fields (always available, not declared in schema):
| Field | Owner | Description |
|---|---|---|
notes | human | Free-text context the agent reads and factors in but never overwrites. The agent skips any row where notes indicates a hold. |
blocked-by | human | Dependency hold — a string describing what must happen before this row can proceed. Rows with a non-empty blocked-by are skipped by the agent and excluded from trigger evaluation. Clear by setting to "" or null. |
last-changed-by | system | Attribution — "agent" or "human". Updated on every write. |
Document lifecycle: new → active → complete → archived. Documents are created in new state at install time. Condition triggers only fire on active documents. Archived documents are read-only and not injected into session context by default.
Export: Available in any lifecycle state via GET /installs/:id/documents/:name/export?format=csv or format=json.
Version history: Every agent write batch creates a named snapshot. Revert to any prior state via POST /installs/:id/documents/:name/snapshots/:snapshotId/revert.
Input / output field definition
{
"type": "string",
"description": "...",
"required": true,
"enum": ["option_a", "option_b"],
"default": "option_a"
}
| Field | Type | Description |
|---|---|---|
type | string | "string", "number", "boolean", "object", "array" |
description | string | Shown to deployers during install and at runtime |
required | boolean | Defaults to false |
enum | string[] | Restricts value to one of these options |
default | any | Default value when the field is not supplied |
Pricing models
free — the team is free to deploy and run. Omit the pricing field entirely for free teams, or set pricing.model: "free" explicitly. Envelope takes no commission on free teams.
{ "model": "free" }
per_run — deployer is charged a flat amount each time the team is invoked. Best for predictable, bounded tasks (triage, classification, summarisation).
{ "model": "per_run", "amount": 0.05, "currency": "usd" }
per_k_tokens — deployer is charged based on total token consumption across all agents in the run, in units of 1,000 tokens. Best for open-ended or variable-length tasks where token usage varies significantly.
{ "model": "per_k_tokens", "amount": 0.012, "currency": "usd" }
subscription (planned — not yet available in v1) — deployer will pay a fixed monthly amount per install. Usage within the install is unlimited. Best for teams used continuously (monitoring agents, always-on assistants).
{ "model": "subscription", "amount": 99, "currency": "usd", "interval": "month" }
Additional subscription fields (for when this model launches):
| Field | Type | Description |
|---|---|---|
interval | string | "month" or "year". Defaults to "month". |
trialDays | number | Free trial length in days. Defaults to 0. Builder-set; Envelope enforces it via Stripe. |
The registry takes a 10% commission on all paid runs. Builders receive 90% via Stripe Connect payouts. Subscription commission rates will be announced when the model launches.
Forking
When a builder forks an existing team, the forkedFrom field records attribution:
{
"forkedFrom": {
"ownerSlug": "acme",
"teamSlug": "support-tier",
"version": "1.2.0"
}
}
The forked team is an independent entity — the builder owns it and sets its own pricing and visibility. The original team's builder is credited in the registry UI. Forking is always permitted for public teams and blocked for private teams. team visibility teams may be forked only by members of the builder's org.
2. Agent definition
Each entry in the agents array.
{
"key": "support-lead",
"name": "Support Lead",
"title": "Head of Support",
"role": "manager",
"adapterType": "http",
"model": "anthropic:claude-sonnet-4-5",
"capabilities": ["triage", "escalation", "reporting"],
"prompt": "You are the Support Lead...",
"accessPolicy": {
"accessPolicyVersion": "1",
"defaultAction": "deny",
"rules": [
{
"host": "api.zendesk.com",
"methods": ["GET", "POST", "PUT", "PATCH"],
"action": "allow",
"reason": "Core Zendesk read/write operations."
},
{
"host": "api.zendesk.com",
"methods": ["DELETE"],
"action": "deny",
"reason": "Ticket deletion is irreversible — disabled for all agents."
},
{
"host": "hooks.slack.com",
"action": "allow",
"reason": "Slack notifications only."
}
]
},
"metadata": { "owner": "support-eng", "reviewedAt": "2026-04-01" }
}
| Field | Type | Required | Description |
|---|---|---|---|
key | string | Yes | Unique within the team. Used in reportsToKey references. Lowercase letters, numbers, and hyphens only ([a-z0-9-]+). |
name | string | Yes | Display name |
title | string | Yes | Job title shown in the team diagram (e.g. "Head of Support") |
role | string | Yes | Free-form description of the agent's role. Suggested values: "manager", "specialist", "analyst" (see role types below), but any string is accepted. |
adapterType | string | No | Runtime adapter (see adapter types). Omitting this field is treated as "http" by all conforming runtimes. Recommended to set explicitly for clarity; required in v2. |
reportsToKey | string | No | key of the parent agent. Omit for top-level (root) agents. |
capabilities | string[] | No | Free-form capability labels (e.g. ["triage", "escalation"]). The reference implementation serialises this as a comma-separated string internally — runtimes must accept the array form and must not reject definitions using it. |
prompt | string | No | System prompt. May reference secrets and variables as {{SECRET_NAME}} and {{VARIABLE_NAME}} |
model | string | No | LLM in "provider:model" format (e.g. "anthropic:claude-opus-4-5"). Shorthand alternative to modelConfig. Supported by all Envelope runtimes. Use this unless you need to set temperature or maxTokens. |
modelConfig | object | No | Full LLM configuration object. Alternative to the flat model field — use one or the other. Allows setting temperature, maxTokens, and per-agent timeout. |
allowedHosts | string[] | No | Simple hostname allowlist. If set, only outbound requests to these hostnames are permitted. Shorthand alternative to a full accessPolicy for straightforward cases. Cannot be combined with accessPolicy. |
accessPolicy | object | No | Structured outbound request rules with per-method and per-path matching (see §6). Use in place of allowedHosts when you need deny rules, path matching, or reason strings. |
metadata | object | No | Arbitrary key-value data preserved by the registry and returned in API responses. Not interpreted by the runtime — for builder use (e.g. internal IDs, review timestamps). |
modelConfig fields (when used instead of flat model):
| Field | Type | Description |
|---|---|---|
provider | string | "anthropic", "openai", "google", "mistral", or "openrouter" |
model | string | Provider-specific model identifier |
temperature | number | 0–2. Defaults to runtime default. |
maxTokens | number | Maximum output tokens per invocation |
timeoutMs | number | Per-agent timeout in ms. Overrides team.timeout.agentMs. |
Secret and variable interpolation in prompts
Agent prompts may reference secrets and variables using double-brace syntax. The runtime replaces these at invocation time — values never appear in the schema file or in transit.
You are the Support Lead for {{COMPANY_NAME}}.
Use the Zendesk API at {{ZENDESK_HOST}} with Bearer token {{ZENDESK_API_KEY}}.
Syntax rules:
| Pattern | Source | Example |
|---|---|---|
{{SECRET_NAME}} | requiredSecrets list | {{ZENDESK_API_KEY}} |
{{VARIABLE_NAME}} | requiredVariables map | {{SUPPORT_EMAIL}} |
{{ENV_VAR}} | Runtime environment (if permitted) | {{RUNTIME_REGION}} |
Rules:
- Names are uppercase with underscores only:
[A-Z][A-Z0-9_]* - An unresolved reference (secret not supplied, variable not set) causes the run to fail immediately with error code
UNRESOLVED_TEMPLATE_REFERENCE - Template interpolation is applied only to the
promptfield. Input values, output definitions, and access policy rules are never interpolated. - Secret values must not appear in run event
inputs,outputs, or error messages — the runtime must redact them
3. Role types
The role field is a free-form string — the registry accepts any value. The following values are the canonical suggestions and are used in the registry UI for display and filtering:
| Role | Description |
|---|---|
manager | Coordinates other agents. Handles escalation routing and task delegation. |
specialist | Executes specific tasks within a defined domain. Receives work from a manager. |
analyst | Monitors output, surfaces insights, and reports. Typically read-only access patterns. |
Custom role strings (e.g. "orchestrator", "reviewer", "qa") are valid and passed through unchanged. Runtimes must not reject definitions with unrecognised role values.
4. Adapter types
| Adapter | Description |
|---|---|
http | HTTP-based cloud runtime. Supported by all Envelope-compatible platforms (Paperclip, Relevance AI, Envelope Managed, and any certified runtime). No local setup required. |
claude_local | Claude Code CLI. Runs locally on the deployer's machine. |
codex_local | OpenAI Codex CLI. Runs locally on the deployer's machine. |
gemini_local | Gemini CLI. Runs locally on the deployer's machine. |
opencode_local | OpenCode open-source coding agent CLI. Best for teams that operate directly on a codebase — cloning, editing, and opening PRs. Deployers supply an API key and repository access. Runs locally on the deployer's machine. |
cursor_local | Cursor IDE agent mode. Runs locally on the deployer's machine. |
Runtimes that implement the Envelope schema must accept the http adapter. Support for local adapters is optional and should be declared in the runtime's conformance profile.
Agent-to-agent communication (http adapter)
The reportsToKey field defines the reporting hierarchy in the schema, but the actual inter-agent communication protocol at runtime is implementation-defined per runtime. Envelope does not mandate a specific wire protocol between agents.
For the http adapter running on Paperclip (Envelope Managed), agents communicate via an internal message bus. The manager agent receives the run inputs, decomposes work, and dispatches tasks to specialist agents as structured messages. Each agent responds to its assigned task; the manager collects results and produces the final output.
Runtimes implementing the schema are free to use any inter-agent protocol (gRPC, HTTP, message queue, in-process calls) provided the external behaviour — inputs in, outputs and run event out — matches the schema contract. The internal wire protocol does not need to be disclosed for conformance certification.
5. Categories reference
The category field accepts one of the following slugs. The registry uses this for browse and filter. Teams may only have one category; use tags for additional classification.
| Slug | Display name |
|---|---|
customer-support | Customer Support |
sales | Sales |
marketing | Marketing |
finance | Finance & Accounting |
hr | HR & People |
devops | DevOps & Infrastructure |
engineering | Engineering |
data-analysis | Data & Analytics |
legal | Legal & Compliance |
research | Research |
content | Content & Writing |
product | Product Management |
security | Security |
operations | Operations |
other | Other |
Teams submitted without a valid category slug are published with category: "other" and flagged for editorial review.
6. Access policy
The accessPolicy field controls which outbound HTTP requests an agent is permitted to make. Rules are evaluated top-to-bottom; the first match wins. If no rule matches, defaultAction applies.
{
"accessPolicyVersion": "1",
"defaultAction": "deny",
"rules": [
{
"host": "api.zendesk.com",
"methods": ["GET", "POST"],
"pathPrefix": "/api/v2/tickets",
"action": "allow"
},
{
"host": "*.internal.co",
"action": "deny",
"reason": "Internal endpoints must never be reachable from agents."
}
]
}
Rules are plain objects — there is no match wrapper. Each rule object has the matching criteria (host, methods, pathPrefix) alongside action and reason at the same level.
| Field | Type | Description |
|---|---|---|
accessPolicyVersion | string | Always "1". Required when the field is present. |
defaultAction | string | "allow" or "deny". Applied when no rule matches. Default is "allow" when omitted — but for agents with a known set of integrations, "deny" is strongly recommended so that unexpected outbound calls are blocked rather than silently permitted. |
rules[].host | string | Hostname to match. Supports leading wildcard (*.domain.com). Omit to match all hosts. |
rules[].methods | string[] | HTTP methods to match (e.g. ["GET", "POST"]). Omit to match all methods. |
rules[].pathPrefix | string | URL path prefix to match (e.g. "/api/v2/tickets"). Omit to match all paths. |
rules[].action | string | "allow", "deny", or "require_approval". See action values below. |
rules[].reason | string | Optional. Shown in block messages and activity logs. Strongly recommended on "deny" rules to help developers diagnose blocked calls. |
Action values:
| Value | v1 behaviour | Notes |
|---|---|---|
"allow" | Request proceeds | |
"deny" | Request blocked; reason surfaced to the agent | |
"require_approval" | Treated as "deny" in v1 | Reserved for v2. When v2 support lands, matched requests will be held for human review rather than blocked outright. Definitions using this value today are forward-compatible — no changes required when runtimes upgrade. |
The field is optional — omitting it is equivalent to defaultAction: "allow" with no rules. Platforms that don't yet support access policy enforcement should ignore the field silently. Platforms must not reject definitions containing "require_approval" — treat it as "deny" in v1. See access-policy.md for the full enforcement design.
7. Human gate
A human gate is a schema-declared review checkpoint between pipeline steps. Gates are declared in the team definition under a top-level gates array. At runtime, the Envelope operator dashboard surfaces each gate as a review tab — the operator decides whether to approve or reject each record before the next step runs.
Human gates are a first-class schema primitive. Runtimes that implement the Envelope schema are expected to surface gates to operators; the specific UI is implementation-defined.
Example:
{
"gates": [
{
"name": "target-review",
"type": "decision",
"afterStep": "research_analyst",
"triggersStep": "email_writer",
"fields": ["githubHandle", "priorityScore", "email", "status"],
"trigger": "any_approved",
"onReject": "skip",
"recordActions": [
{
"label": "Approve",
"verb": "approve",
"endpointTemplate": "/installs/{installId}/targets/{githubHandle}/approve",
"style": "primary"
},
{
"label": "Skip",
"verb": "skip",
"endpointTemplate": "/installs/{installId}/targets/{githubHandle}/skip",
"style": "secondary"
}
],
"timeout": {
"after": "72h",
"behaviour": "escalate"
}
}
]
}
HumanGate fields:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Unique name for this gate within the pipeline. Used as a stable identifier in webhook payloads and audit logs. |
type | string | Yes | The nature of the human decision (see gate types below). |
afterStep | string | Yes | Key of the pipeline step whose output produces the records waiting for review. |
triggersStep | string | Yes | Key of the pipeline step that runs once the gate trigger condition is met. |
fields | string[] | Yes | Record field names to surface in the review UI — what the operator needs to make a decision. |
trigger | string | Yes | When to release the gate and run the next step (see trigger values below). |
thresholdCount | number | Conditional | Required when trigger is "threshold". Number of approved records needed to release the gate. |
onReject | string | Yes | What happens when a human rejects a record (see reject behaviour below). |
recordActions | object[] | No | Per-record actions available in the review table. If omitted, the runtime may use defaults (approve / skip). |
timeout | object | Conditional | What to do if no action is taken within a period. Required when trigger is "scheduled" — the timeout.after duration acts as the schedule interval. Optional for all other trigger types. |
timeout.after | string | — | Duration string, e.g. "72h", "30m". |
timeout.behaviour | string | — | "escalate" — route to coordinator with a timeout notice. "halt" — stop the pipeline and notify. "proceed" — release the gate and continue. |
Gate types (type):
| Value | Description |
|---|---|
decision | Yes/no on candidate records — e.g. approving enriched leads or research results before the next agent acts on them. |
content_generation | Approve or edit agent-produced text before it is used — e.g. reviewing email drafts before sending. |
classification | Spot-check or override agent categorisation — e.g. moderation decisions, support ticket triage. |
action | Approve before an irreversible action fires — e.g. confirming an email send, a payment, or a post. |
Trigger values (trigger):
| Value | Description |
|---|---|
any_approved | Release the gate and run the next step as soon as at least one record is approved. Additional records may still be reviewed concurrently. |
all_resolved | Wait until every record has a decision (approved or rejected) before proceeding. |
threshold | Release when N records are approved. Set thresholdCount to specify N (see fields table above). |
scheduled | Release after the duration in timeout.after elapses from gate creation, regardless of how many records have been approved. Requires timeout to be set — the duration is the schedule. |
Reject behaviour (onReject):
| Value | Description |
|---|---|
skip | Exclude the rejected record from the next step and move on. The record is marked rejected in the gate log. |
rerun | Pass the rejection reason back to the previous step and re-run it for this record only. |
escalate | Route the rejection to the coordinator agent with the human's reason attached. The coordinator decides the next action. |
halt | Stop the entire pipeline and notify the operator. Use for cases where a single rejection means the run cannot continue. |
recordActions fields:
| Field | Type | Description |
|---|---|---|
label | string | Button label shown in the review UI. |
verb | string | Machine-readable action. One of "approve", "skip", "reject", "reset". |
endpointTemplate | string | API endpoint to call when the button is clicked. Use {fieldName} interpolation for record identifiers (e.g. {githubHandle}, {id}). {installId} is injected automatically by the runtime. |
style | string | "primary" (green/highlighted), "secondary" (neutral), "danger" (red). |
Install contract
The install contract defines what happens when a deployer installs a team. Runtimes that implement the Envelope schema must honour this sequence.
Install lifecycle
Deployer selects team in registry
↓
Registry validates team definition against current schema version
↓
Deployer supplies requiredSecrets and requiredVariables
↓
Registry sends InstallRequest to runtime
↓
Runtime provisions agents, injects secrets and variables
↓
Runtime returns InstallResult (provisioned agent IDs, dashboard URL)
↓
Registry stores install record, webhook fired: install.created
InstallRequest (registry → runtime)
{
"installId": "ins_a1b2c3d4",
"teamSlug": "support-tier",
"teamVersion": "1.2.0",
"teamDefinition": { ... },
"secrets": {
"ZENDESK_API_KEY": "enc:v1:...",
"SLACK_BOT_TOKEN": "enc:v1:..."
},
"variables": {
"SUPPORT_EMAIL": "[email protected]"
},
"deployerOrgId": "org_xyz",
"requestedAt": "2026-04-14T10:00:00Z"
}
Secrets are transmitted encrypted. The runtime decrypts using the shared key established during the platform's Envelope certification process. Plain-text secret values must never appear in logs, error responses, or audit records.
InstallResult (runtime → registry)
{
"installId": "ins_a1b2c3d4",
"status": "active",
"provisionedAgents": [
{
"key": "support-lead",
"runtimeId": "agent_runtime_001",
"endpoint": "https://runtime.example.com/agents/agent_runtime_001"
}
],
"dashboardUrl": "https://runtime.example.com/installs/ins_a1b2c3d4",
"provisionedAt": "2026-04-14T10:00:05Z"
}
| Field | Type | Description |
|---|---|---|
installId | string | Echoed from the request |
status | string | "active", "failed", or "pending" |
provisionedAgents | object[] | One entry per agent in the team definition |
provisionedAgents[].key | string | Matches the key field in the agent definition |
provisionedAgents[].runtimeId | string | Runtime's internal identifier for the agent |
provisionedAgents[].endpoint | string | URL where the agent accepts invocations |
dashboardUrl | string | URL the deployer can visit to manage the install |
provisionedAt | string | ISO 8601 timestamp |
warnings | string[] | Non-fatal issues the runtime encountered during provisioning. Returned even on "active" status. Surfaces to the deployer in the Envelope UI after install. |
error | object | Present only when status is "failed". See error object below. |
error.code | string | Machine-readable error code (see runtime error codes). |
error.message | string | Human-readable description of the failure. |
error.retryable | boolean | Whether retrying the install request (after fixing the indicated issue) is expected to succeed. |
If status is "failed", the runtime must include an error field:
{
"installId": "ins_a1b2c3d4",
"status": "failed",
"error": {
"code": "MISSING_SECRET",
"message": "ZENDESK_API_KEY was not supplied",
"retryable": false
}
}
Envelope Managed install path
When platform is "envelope" in the install request, Envelope itself is the runtime — there is no external InstallRequest/InstallResult handshake. The Envelope API handles provisioning internally and returns the install record directly to the caller via POST /api/installs. The requesting API key is automatically linked to the new installId, and the operator dashboard is available immediately at /pipelines?install={installId}.
Differences from the external runtime flow:
| Aspect | External runtime | Envelope Managed |
|---|---|---|
| InstallRequest sent | Yes — to the runtime endpoint | No — handled internally |
| Credential fields | Required (platform API keys etc.) | None — no external platform |
| Post-install secrets | Deployer supplies before install | Added by the deployer after install via PATCH /installs/{id}/secrets and PATCH /installs/{id}/variables |
| API key link | Manual association | Automatic — the requesting key's installId is set on success |
| Billing | Platform-native | Envelope billing (per run or per 1K tokens) |
Run event schema
A run is a single invocation of an installed team. Every run produces a run event. The run event schema is the same regardless of which runtime executed the team.
{
"runId": "run_9f8e7d6c",
"installId": "ins_a1b2c3d4",
"teamSlug": "support-tier",
"teamVersion": "1.2.0",
"status": "completed",
"triggerSource": "event:webhook:wh_abc123",
"startedAt": "2026-04-14T10:01:00Z",
"completedAt": "2026-04-14T10:01:04.312Z",
"durationMs": 4312,
"inputs": {
"ticket_text": "My order hasn't arrived after 14 days."
},
"outputs": {
"resolution": "Escalated to Tier 2. Refund initiated.",
"assigned_agent": "support-lead",
"escalated": true
},
"usage": {
"totalTokens": 1842,
"promptTokens": 1210,
"completionTokens": 632,
"agentBreakdown": [
{
"agentKey": "support-lead",
"promptTokens": 840,
"completionTokens": 312
},
{
"agentKey": "tier-2-specialist",
"promptTokens": 370,
"completionTokens": 320
}
]
},
"error": null,
"_envelope": {
"generatedBy": "Envelope · openenvelope.org",
"runId": "run_9f8e7d6c",
"workspaceUrl": "https://app.openenvelope.org"
}
}
| Field | Type | Description |
|---|---|---|
runId | string | Globally unique run identifier |
installId | string | The install that was invoked |
teamSlug | string | Team slug at time of run |
teamVersion | string | Team version at time of run |
status | string | Final states: "completed", "failed", "timeout", "cancelled". Transient states (async polling only): "pending" (queued, not yet started), "running" (in progress). Final events stored in the run log always carry a terminal status. |
triggerSource | string | null | What fired this run. null for manual invocations. Examples: "event:webhook:wh_abc123" (inbound webhook), "condition:deal-idle-check" (condition trigger), "cron" (scheduled). |
startedAt | string | ISO 8601 timestamp |
completedAt | string | ISO 8601 timestamp. Null if still running. |
durationMs | number | Wall-clock time in milliseconds |
inputs | object | Input values as supplied by the caller |
outputs | object | null | Output values returned by the team. null on failure or cancellation. |
usage.totalTokens | number | Total tokens consumed across all agents |
usage.promptTokens | number | Total prompt (input) tokens across all agents |
usage.completionTokens | number | Total completion (output) tokens across all agents |
usage.agentBreakdown | object[] | Per-agent token usage. Each entry includes agentKey, promptTokens, and completionTokens. |
error | object | null | Populated on failure (see error object below). null when the run succeeded. |
_envelope | object | Attribution metadata. Always present. _envelope.generatedBy is "Envelope · openenvelope.org". _envelope.runId echoes the run ID. _envelope.workspaceUrl is the Envelope workspace URL. |
Error object:
{
"code": "AGENT_TIMEOUT",
"message": "support-lead did not respond within 30s",
"agentKey": "support-lead",
"retryable": true
}
Run invocation
Authentication
Deployers authenticate run invocations using a deployer token — a per-install token issued by the registry at install time and stored in the deployer's Envelope dashboard. This token is scoped to a single install and cannot be used to access other installs or registry management operations.
POST /envelope/v1/installs/{installId}/runs
Authorization: Bearer dep_live_xxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
{
"inputs": {
"ticket_text": "My order hasn't arrived after 14 days."
}
}
Deployer tokens are distinct from builder API keys (env_live_...). Builder keys are for registry management (publish, update, delete). Deployer tokens are for runtime invocation only.
Synchronous invocation (default)
By default, run requests are synchronous. The HTTP connection stays open until the run completes and returns the full run event. Suitable for runs that complete within the timeout window.
HTTP 200
{
"runId": "run_9f8e7d6c",
"status": "completed",
"outputs": { ... },
"usage": { ... },
"durationMs": 4312
}
Asynchronous invocation (long-running runs)
For runs that may exceed 30s, pass "async": true in the request body. The endpoint returns immediately with 202 Accepted and a run ID. Poll for completion or receive the result via webhook.
POST /envelope/v1/installs/{installId}/runs
{
"inputs": { ... },
"async": true
}
HTTP 202
{
"runId": "run_9f8e7d6c",
"status": "pending",
"pollUrl": "/envelope/v1/installs/{installId}/runs/run_9f8e7d6c"
}
Polling:
GET /envelope/v1/installs/{installId}/runs/run_9f8e7d6c
→ { "status": "running", "startedAt": "...", "durationMs": 12500 }
→ { "status": "completed", "outputs": { ... }, "durationMs": 87200 }
Poll with exponential backoff starting at 1s. The runtime returns Retry-After headers on in-progress responses as a hint. Do not poll more frequently than 1s.
Streaming output (SSE)
For runs that produce incremental output (e.g. a writing or research team), pass "stream": true. The runtime responds with a Server-Sent Events stream. Each event is a partial run event; the final event has "status": "completed" and the full outputs object.
POST /envelope/v1/installs/{installId}/runs
Accept: text/event-stream
{ "inputs": { ... }, "stream": true }
data: {"type":"agent.start","agentKey":"support-lead","timestamp":"..."}
data: {"type":"agent.token","agentKey":"support-lead","token":"Escalat"}
data: {"type":"agent.token","agentKey":"support-lead","token":"ing to"}
data: {"type":"agent.complete","agentKey":"support-lead","durationMs":3100}
data: {"type":"run.complete","status":"completed","outputs":{...},"usage":{...}}
Streaming is optional for conformance. Runtimes that do not support streaming must return HTTP 400 with code STREAMING_NOT_SUPPORTED when stream: true is passed — not silently fall back to synchronous.
Run cancellation
An in-flight async run can be cancelled:
DELETE /envelope/v1/installs/{installId}/runs/{runId}
HTTP 200
{ "runId": "run_9f8e7d6c", "status": "cancelled", "cancelledAt": "..." }
Cancellation is best-effort. If the run has already completed before the cancellation reaches the runtime, the response reflects the completed state and the cancellation is a no-op. Billing is not reversed for a run that completed before cancellation.
Input validation
The runtime must validate the inputs object against the team's declared inputs schema before starting the run. Invalid inputs must fail immediately with INVALID_INPUT — not mid-run. The registry also validates inputs at the API gateway layer as a first line of defence.
Output validation
Runtimes should validate their own output against the declared outputs schema. If validation fails, the run is marked "failed" with code OUTPUT_SCHEMA_VIOLATION rather than returning data that contradicts the published contract. This protects deployers who build on declared output shapes.
Quota response headers
Every run invocation response (success or failure) includes quota headers:
X-Envelope-Runs-Remaining: 847
X-Envelope-Runs-Limit: 1000
X-Envelope-Runs-Reset: 2026-05-01T00:00:00Z
X-Envelope-Tokens-Remaining: 2847291
X-Envelope-Tokens-Limit: 5000000
These reflect the deployer org's current billing period quota. Runtimes must return these headers on all /runs endpoints.
Scheduling
Teams can be configured to fire automatically. There are two independent scheduling layers:
- Team-level schedule — set by the builder and applies to all installs of that team by default.
- Install-level schedule override — set by the deployer for a specific install. When present, the install-level schedule replaces the team-level one for that install only.
Both layers use the same ScheduleSpec fields. Three trigger types are available as of v1.1.0.
Trigger types
type | Description |
|---|---|
manual | User-initiated only. No automatic firing. |
cron | Time-based schedule. Requires cronExpression and timezone. |
event | Fires when an inbound webhook arrives. Requires webhookSecret for signature verification. |
condition | Polls at a configured interval; fires only if a lightweight AI pre-check returns a positive signal. Requires conditionPrompt. |
continuous | Persistent observation loop — Local daemon only. Enum value reserved; config fields are deferred to a future release. |
ScheduleSpec fields
| Field | Type | Required | Description |
|---|---|---|---|
type | string | No | Trigger type (see table above). Defaults to "manual" when omitted. |
cronExpression | string | Conditional | Standard 5-field cron expression: minute hour dom month dow. E.g. "0 9 * * 1-5" = weekdays at 09:00. Required when type is "cron". |
timezone | string | Conditional | IANA timezone name. E.g. "Europe/London", "America/New_York", "UTC". Required when type is "cron". |
webhookSecret | string | Conditional | HMAC-SHA256 secret for verifying inbound webhook payloads. Generated by Envelope. Required when type is "event". |
conditionPrompt | string | Conditional | Plain-English condition evaluated on each polling interval. The full team runs only if the model returns a positive signal. Required when type is "condition". |
conditionInterval | string | No | Polling interval for condition checks. One of "15min", "1h", "6h", "24h". Defaults to "1h". Only applies when type is "condition". |
label | string | No | Human-readable name for the schedule (e.g. "Daily at 09:00 London"). Shown in the operator dashboard. |
onBacklog | string | No | What to do when the schedule fires but the previous run is still in progress: "skip" (default) or "queue". |
bypassGates | boolean | No | If true, human gate checkpoints are skipped and the next step runs automatically. Defaults to false. |
enabled | boolean | Install-level only | Whether the schedule is active. Set to false to pause without deleting. |
Cron expression format:
┌───── minute (0–59)
│ ┌───── hour (0–23, interpreted in the timezone field)
│ │ ┌───── day of month (1–31)
│ │ │ ┌───── month (1–12)
│ │ │ │ ┌───── day of week (0=Sunday – 6=Saturday)
│ │ │ │ │
* * * * *
Common patterns:
| Expression | Description |
|---|---|
0 9 * * 1-5 | Weekdays at 09:00 |
0 9 * * * | Every day at 09:00 |
0 */4 * * * | Every 4 hours |
30 8 1 * * | First of every month at 08:30 |
Team-level schedule API
Builders set a team-level schedule via the templates API. This schedule applies to all existing and future installs of that template unless overridden at the install level.
Cron trigger:
PUT /api/templates/{templateId}/schedule
Authorization: Bearer env_live_xxxx
Content-Type: application/json
{
"type": "cron",
"cronExpression": "0 9 * * 1-5",
"timezone": "Europe/London",
"label": "Weekday morning run",
"onBacklog": "skip",
"bypassGates": false
}
Event trigger:
PUT /api/templates/{templateId}/schedule
Authorization: Bearer env_live_xxxx
Content-Type: application/json
{
"type": "event",
"label": "Fires on inbound webhook"
}
The webhookSecret is generated by Envelope on save and returned in the response. Do not pass it in the request body.
Condition trigger:
PUT /api/templates/{templateId}/schedule
Authorization: Bearer env_live_xxxx
Content-Type: application/json
{
"type": "condition",
"conditionPrompt": "Are there any open support tickets with priority 'urgent' that haven't been updated in the last 4 hours?",
"conditionInterval": "1h",
"label": "Hourly urgent ticket check"
}
Delete a team schedule:
DELETE /api/templates/{templateId}/schedule
Install-level schedule override
Deployers set a per-install override via the installs API. When an install has its own schedule, the team-level schedule is ignored for that install.
PATCH /installs/{installId}/schedule
Authorization: Bearer env_live_xxxx
Content-Type: application/json
{
"type": "cron",
"cronExpression": "0 6 * * *",
"timezone": "America/New_York",
"label": "Early morning run (NYC)",
"onBacklog": "skip",
"bypassGates": false,
"enabled": true
}
Set enabled: false to pause without deleting, or omit the body and send DELETE /installs/{installId}/schedule to remove the override entirely (the team-level schedule resumes).
Get the current schedule for an install:
GET /installs/{installId}/schedule
Returns the active schedule spec plus lastRunAt and nextRunAt as ISO 8601 timestamps (or null if no schedule is configured).
Inbound webhooks (event triggers)
When a team uses the event trigger type, Envelope generates a unique inbound webhook URL and HMAC-SHA256 signing secret. External systems POST to this URL to fire a run.
Endpoint
POST /webhooks/team/{webhookId}
X-Envelope-Signature: sha256=<hmac>
Content-Type: application/json
{
"eventType": "ticket.created",
"payload": { ... }
}
The request body is forwarded as-is to the team run as input context. Any JSON object is accepted — Envelope does not enforce a specific body shape.
Signature verification
Every inbound request must include an X-Envelope-Signature header. The value is sha256= followed by the HMAC-SHA256 hex digest of the raw request body, computed using the webhookSecret configured on the schedule.
X-Envelope-Signature: sha256=b94d27b9934d3e08a52e52d7da7dabfac484efe04294e576a25c0c396d9f3e54
Requests with a missing, malformed, or invalid signature are rejected with 401 Unauthorized. Requests with a valid signature are accepted and a run is dispatched immediately.
Verifying the signature (Node.js example):
import { createHmac, timingSafeEqual } from 'crypto';
function verifyEnvelopeSignature(
rawBody: Buffer,
signatureHeader: string,
secret: string
): boolean {
const expected = 'sha256=' + createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
const actual = Buffer.from(signatureHeader);
const expectedBuf = Buffer.from(expected);
if (actual.length !== expectedBuf.length) return false;
return timingSafeEqual(actual, expectedBuf);
}
Use timingSafeEqual to prevent timing attacks — never compare signature strings with ===.
Responses
| Status | Meaning |
|---|---|
202 Accepted | Signature valid; run dispatched. Body: { "runId": "run_..." } |
401 Unauthorized | Missing or invalid X-Envelope-Signature |
404 Not Found | webhookId does not exist or has been deleted |
429 Too Many Requests | Rate limit exceeded — back off and retry |
Secret rotation
To rotate the webhook secret, call:
POST /api/installs/{installId}/webhooks/{webhookId}/rotate
Authorization: Bearer env_live_xxxx
The new secret is returned in the response. The old secret is immediately invalidated — update the source system before rotating.
Webhook management
| Method | Path | Description |
|---|---|---|
GET | /api/installs/{installId}/webhooks | List all webhook endpoints for an install |
POST | /api/installs/{installId}/webhooks | Register a new webhook endpoint |
DELETE | /api/installs/{installId}/webhooks/{webhookId} | Remove a webhook endpoint |
POST | /api/installs/{installId}/webhooks/{webhookId}/rotate | Rotate the signing secret |
Webhook payloads
Envelope sends webhooks to URLs registered by builders and deployers. All payloads share a common envelope:
{
"id": "evt_1a2b3c4d",
"type": "run.completed",
"sentAt": "2026-04-14T10:01:05Z",
"data": { ... }
}
Webhook requests include a X-Envelope-Signature header — an HMAC-SHA256 signature of the raw request body using the webhook secret. Receivers must validate this before processing.
Event types
| Event | Trigger | Subscriber |
|---|---|---|
install.created | A team is successfully installed | Builder, Org |
install.removed | An install is uninstalled | Builder, Org |
install.failed | Install provisioning failed | Deployer, Org |
run.completed | A run finishes successfully | Builder, Deployer, Org |
run.failed | A run fails or times out | Builder, Deployer, Org |
approval.requested | An install requires governance approval | Org admin |
approval.decided | An approval is accepted or rejected | Deployer |
billing.payment_failed | A subscription payment fails | Org |
billing.trial_ending | Trial expires in 3 days | Org |
secrets.rotated | A deployer rotates one or more install secrets | Deployer, Org |
install.created
{
"id": "evt_1a2b3c4d",
"type": "install.created",
"sentAt": "2026-04-14T10:00:06Z",
"data": {
"installId": "ins_a1b2c3d4",
"teamSlug": "support-tier",
"teamVersion": "1.2.0",
"deployerOrgId": "org_xyz",
"deployerOrgName": "Acme Corp",
"installedAt": "2026-04-14T10:00:05Z"
}
}
run.completed / run.failed
The data object is the full run event schema (see above).
approval.requested
{
"id": "evt_2b3c4d5e",
"type": "approval.requested",
"sentAt": "2026-04-14T10:00:01Z",
"data": {
"approvalId": "apr_001",
"installId": "ins_a1b2c3d4",
"teamSlug": "support-tier",
"requestedBy": "[email protected]",
"orgSlug": "acme-corp",
"reviewUrl": "https://app.openenvelope.org/orgs/acme-corp/governance"
}
}
install.removed
{
"id": "evt_3c4d5e6f",
"type": "install.removed",
"sentAt": "2026-04-20T14:30:00Z",
"data": {
"installId": "ins_a1b2c3d4",
"teamSlug": "support-tier",
"teamVersion": "1.2.0",
"deployerOrgId": "org_xyz",
"removedAt": "2026-04-20T14:30:00Z"
}
}
install.failed
{
"id": "evt_4d5e6f7g",
"type": "install.failed",
"sentAt": "2026-04-20T14:30:05Z",
"data": {
"installId": "ins_a1b2c3d4",
"teamSlug": "support-tier",
"teamVersion": "1.2.0",
"deployerOrgId": "org_xyz",
"error": {
"code": "MISSING_SECRET",
"message": "ZENDESK_API_KEY was not supplied",
"retryable": false
},
"failedAt": "2026-04-20T14:30:05Z"
}
}
approval.decided
{
"id": "evt_5e6f7g8h",
"type": "approval.decided",
"sentAt": "2026-04-20T15:00:00Z",
"data": {
"approvalId": "apr_001",
"installId": "ins_a1b2c3d4",
"teamSlug": "support-tier",
"decision": "approved",
"decidedBy": "[email protected]",
"orgSlug": "acme-corp",
"decidedAt": "2026-04-20T15:00:00Z"
}
}
decision is "approved" or "rejected". On rejection, an optional reason string may be present.
secrets.rotated
{
"id": "evt_6f7g8h9i",
"type": "secrets.rotated",
"sentAt": "2026-04-20T16:00:00Z",
"data": {
"installId": "ins_a1b2c3d4",
"rotatedKeys": ["ZENDESK_API_KEY"],
"rotatedBy": "[email protected]",
"rotatedAt": "2026-04-20T16:00:00Z"
}
}
rotatedKeys lists the names of the secrets that were updated — never the values.
Webhook registration API
Builders and deployers register webhook URLs via the registry API. Webhook registrations are scoped to a team (builder) or an install (deployer).
Register a webhook:
POST /registry/v1/webhooks
Authorization: Bearer env_live_xxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
{
"url": "https://your-server.com/envelope-webhook",
"events": ["install.created", "run.failed"],
"scope": "team",
"teamSlug": "support-tier"
}
| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes | HTTPS endpoint. Must be publicly reachable. HTTP is rejected. |
events | string[] | Yes | List of event types to receive. Use ["*"] for all events in scope. |
scope | string | Yes | "team" (builder receives events for their team), "install" (deployer receives events for their install), or "org" (org admins receive billing and governance events) |
teamSlug | string | Conditional | Required when scope is "team" |
installId | string | Conditional | Required when scope is "install" |
orgSlug | string | Conditional | Required when scope is "org" |
Response:
{
"webhookId": "wh_abc123",
"url": "https://your-server.com/envelope-webhook",
"secret": "whsec_xxxxxxxxxxxxxxxxxxxxxxxx",
"events": ["install.created", "run.failed"],
"createdAt": "2026-04-14T10:00:00Z"
}
The secret is returned only on creation and never again. Store it securely — use it to validate the X-Envelope-Signature header on incoming events.
Manage webhooks:
| Method | Path | Description |
|---|---|---|
GET | /registry/v1/webhooks | List all webhooks for the authenticated key |
GET | /registry/v1/webhooks/{webhookId} | Get webhook details (secret not returned) |
PATCH | /registry/v1/webhooks/{webhookId} | Update URL or event list |
DELETE | /registry/v1/webhooks/{webhookId} | Remove a webhook |
POST | /registry/v1/webhooks/{webhookId}/test | Send a synthetic webhook.test event to verify reachability |
Webhook delivery and retry policy
Envelope delivers webhooks with the following guarantees:
- At-least-once delivery — webhooks may be delivered more than once in rare failure cases. Receivers must be idempotent (use
event.idas a deduplication key). - Timeout — Envelope waits 10s for a
2xxresponse. Non-2xx or timeout triggers a retry. - Retry schedule — 5s, 30s, 5min, 30min, 2h, 8h, 24h (7 attempts total). After all retries fail, the event is marked
failedand the webhook is flagged in the Envelope dashboard. - Order — events within a single install or team are delivered in order. Events across different installs may arrive out of order.
- Disabled webhooks — a webhook that fails all retries for 3 consecutive events is automatically disabled. Builders are notified via email.
Registry protocol
The registry protocol defines how a team is resolved, versioned, and deprecated. Any compliant registry must implement this surface.
Slug resolution
Teams are addressed by {ownerSlug}/{teamSlug} or simply {teamSlug} when the owner is unambiguous.
GET /registry/v1/teams/{ownerSlug}/{teamSlug}
GET /registry/v1/teams/{ownerSlug}/{teamSlug}/versions
GET /registry/v1/teams/{ownerSlug}/{teamSlug}/versions/{version}
Versioning semantics
Teams follow semver. The registry enforces:
PATCH— backwards-compatible fixes. No re-install required for existing installs.MINOR— new optional inputs/outputs, new agents withreportsToKeypointing to existing agents. Existing installs continue working; new installs get new behaviour.MAJOR— breaking change. Existing installs are not automatically migrated. The registry marks the previous major asdeprecatedand notifies deployers.
What constitutes a MAJOR (breaking) change:
- Removing a declared
inputsoroutputsfield that deployers depend on - Renaming a required input field or changing its type
- Removing an agent from the
agentsarray (callers may reference itskeyin pipelines) - Renaming an agent
key - Changing the
adapterTypeof an existing agent in a way that changes its execution model - Making a previously optional input required
- Removing a declared
requiredSecretsorrequiredVariablesentry that the runtime relied on
When in doubt, prefer a MINOR bump for additions and a MAJOR bump for anything that removes or renames. The registry does not validate break-detection automatically in v1 — it is the builder's responsibility to increment correctly.
A version is immutable once published. Builders must increment the version to change a published definition. The registry rejects a publish request if the version already exists.
Deprecation
When a builder deprecates a version or a full team:
PATCH /registry/v1/teams/{ownerSlug}/{teamSlug}/versions/{version}
{ "deprecated": true, "deprecationMessage": "Use support-tier-v2 instead." }
Existing installs continue to function. Deployers with deprecated installs receive a notification. The registry surfaces the deprecation warning in the UI and via the API.
Publishing a team
Builders authenticate to the registry API using a personal API key or an org API key. Keys are created in the Envelope dashboard and passed as a bearer token.
Authorization: Bearer env_live_xxxxxxxxxxxxxxxxxxxx
Publish a new version:
POST /registry/v1/teams/{ownerSlug}/{teamSlug}/versions
Content-Type: application/json
{
"$schema": "https://schema.openenvelope.org/team/v1.json",
"name": "Support Tier",
"slug": "support-tier",
"version": "1.3.0",
...
}
The registry validates the definition against the current JSON Schema before accepting it. A 400 response is returned with validation errors if the definition is invalid. A 409 is returned if the version already exists (versions are immutable).
Include an optional changelog string to document what changed in this version — shown in the registry UI and returned by the versions API:
{
"$schema": "https://schema.openenvelope.org/team/v1.json",
"version": "1.3.0",
"changelog": "Added priority_override input. Fixed escalation routing for overnight tickets.",
...
}
Update team metadata (non-breaking):
PATCH /registry/v1/teams/{ownerSlug}/{teamSlug}
{ "visibility": "public", "category": "customer-support" }
Only top-level metadata fields (visibility, category, tags, name, description) may be updated this way. To change agents, inputs, outputs, pricing, or requiredSecrets, publish a new version.
Full publishing API:
| Method | Path | Description |
|---|---|---|
POST | /registry/v1/teams/{ownerSlug}/{teamSlug}/versions | Publish a new version |
GET | /registry/v1/teams/{ownerSlug}/{teamSlug}/versions | List all versions |
GET | /registry/v1/teams/{ownerSlug}/{teamSlug}/versions/{version} | Fetch a specific version |
PATCH | /registry/v1/teams/{ownerSlug}/{teamSlug} | Update team metadata |
PATCH | /registry/v1/teams/{ownerSlug}/{teamSlug}/versions/{version} | Deprecate a version |
DELETE | /registry/v1/teams/{ownerSlug}/{teamSlug} | Delete team (only if zero installs) |
Search and discovery
GET /registry/v1/search?q={query}&category={category}&tag={tag}&adapter={adapter}
Returns paginated results with team metadata. The full team definition is not included in search results — fetch it separately using the slug endpoint.
Install management
Secret rotation
If a deployer needs to update a secret after install (key rotation, credential expiry), they can patch the install's secrets without reinstalling:
PATCH /envelope/v1/installs/{installId}/secrets
Authorization: Bearer dep_live_xxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
{
"ZENDESK_API_KEY": "zd_live_newkeyvalue1234"
}
Deployers pass secret values as plain text — the registry encrypts them in transit before forwarding to the runtime. Never pre-encrypt values: the registry rejects enc:v1:... prefixed strings in this endpoint and will return SECRET_ENCRYPTION_ERROR.
Only the secrets listed in the request body are updated. Secrets not listed are left unchanged. The runtime re-injects the new values into all provisioned agents within the install. There is no downtime — agents in flight at the moment of rotation complete with the old secret; new invocations receive the updated value.
After rotation, a secrets.rotated audit event is written and, if the deployer has a secrets.rotated webhook registered, it fires.
Install health check
GET /envelope/v1/installs/{installId}/health
Authorization: Bearer dep_live_xxxxxxxxxxxxxxxxxxxx
HTTP 200
{
"installId": "ins_a1b2c3d4",
"status": "healthy",
"agents": [
{ "key": "support-lead", "status": "healthy", "lastChecked": "2026-04-14T10:00:00Z" },
{ "key": "tier-2-specialist", "status": "degraded", "reason": "MODEL_UNAVAILABLE" }
],
"checkedAt": "2026-04-14T10:00:01Z"
}
| Agent status | Description |
|---|---|
healthy | Agent is reachable and the runtime reports it ready to accept runs |
degraded | Agent is reachable but one or more dependencies are unavailable |
unhealthy | Agent is unreachable or in a fatal error state |
The install-level status is healthy only if all agents are healthy. If any agent is degraded, the install is degraded. If any agent is unhealthy, the install is unhealthy.
Standard error codes
All error responses from the registry and from compliant runtimes use a consistent shape:
{
"error": {
"code": "MISSING_SECRET",
"message": "Human-readable description",
"field": "ZENDESK_API_KEY",
"retryable": false
}
}
Registry error codes
| Code | HTTP | Description |
|---|---|---|
INVALID_SCHEMA | 400 | The team definition does not validate against the Envelope JSON Schema |
VERSION_EXISTS | 409 | A version with this semver already exists (versions are immutable) |
SLUG_TAKEN | 409 | The slug is already registered by another owner |
SLUG_INVALID | 400 | The slug contains invalid characters or is reserved |
VISIBILITY_NOT_PERMITTED | 403 | The org plan does not allow private visibility |
TEAM_HAS_INSTALLS | 409 | Cannot delete a team that has active installs |
VERSION_NOT_FOUND | 404 | The requested team or version does not exist |
AUTH_REQUIRED | 401 | No valid API key was supplied |
FORBIDDEN | 403 | The API key does not have permission for this action |
RATE_LIMITED | 429 | Too many requests — back off and retry after the Retry-After header value |
Runtime error codes
Returned in the error field of a failed InstallResult or run event:
| Code | Retryable | Description |
|---|---|---|
MISSING_SECRET | No | A secret listed in requiredSecrets was not supplied at install time |
MISSING_VARIABLE | No | A variable listed in requiredVariables was not supplied |
UNRESOLVED_TEMPLATE_REFERENCE | No | A {{PLACEHOLDER}} in a prompt could not be resolved |
AGENT_TIMEOUT | Yes | An agent did not respond within the configured timeout |
RUN_TIMEOUT | No | The entire run exceeded timeout.runMs |
MODEL_UNAVAILABLE | Yes | The configured LLM provider returned a 5xx or rate limit error |
ACCESS_POLICY_VIOLATION | No | An agent attempted an outbound request blocked by its access policy |
PROVISIONING_FAILED | Yes | The runtime could not provision one or more agents |
RUN_LIMIT_EXCEEDED | No | The install has exceeded its run quota for the billing period |
TOKEN_LIMIT_EXCEEDED | No | The run consumed more tokens than the org's token quota for the billing period |
INVALID_INPUT | No | The run inputs do not match the team's declared input schema |
OUTPUT_SCHEMA_VIOLATION | No | The runtime's output does not match the team's declared output schema |
STREAMING_NOT_SUPPORTED | No | stream: true was passed but this runtime does not support SSE streaming |
RUN_CANCELLED | No | The run was cancelled via DELETE /runs/{runId} before completion |
SECRET_ENCRYPTION_ERROR | No | A secret value could not be decrypted — the install may need to be re-provisioned |
INSTALL_NOT_FOUND | No | The referenced installId does not exist or has been removed |
INTERNAL_RUNTIME_ERROR | Yes | An unclassified internal error occurred in the runtime |
retryable: true means the caller may retry the same request without modification after a brief wait. retryable: false means the error will recur without a change to configuration or inputs.
API surface (conformance required)
Any runtime claiming Envelope compatibility must implement the following endpoints. Request and response shapes must match exactly.
Install lifecycle:
| Method | Path | Description |
|---|---|---|
POST | /envelope/v1/installs | Provision a new install from an InstallRequest |
GET | /envelope/v1/installs/{installId} | Return current install status and provisionedAgents |
DELETE | /envelope/v1/installs/{installId} | Remove an install and deprovision all agents |
GET | /envelope/v1/installs/{installId}/health | Return health status for the install and each agent |
PATCH | /envelope/v1/installs/{installId}/secrets | Rotate one or more secrets without reinstalling |
Run invocation:
| Method | Path | Description |
|---|---|---|
POST | /envelope/v1/installs/{installId}/runs | Invoke the team (sync, async, or stream) |
GET | /envelope/v1/installs/{installId}/runs/{runId} | Fetch a run event by ID (also used for async polling) |
DELETE | /envelope/v1/installs/{installId}/runs/{runId} | Cancel an in-flight run |
GET | /envelope/v1/installs/{installId}/runs | List runs for an install (paginated) |
Runtime:
| Method | Path | Description |
|---|---|---|
GET | /envelope/v1/health | Returns { "status": "ok" } — used for conformance checks |
Authentication to the runtime API uses a bearer token issued during the certification handshake. The token is scoped to the registry — not to a user. Deployer tokens (dep_live_...) are passed by callers invoking runs and must be validated by the runtime against the registry.
Pagination
All list endpoints use cursor-based pagination. Page/offset pagination is not supported.
Request parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | number | 20 | Maximum results to return. Max 100. |
after | string | — | Cursor from a previous response's nextCursor. Omit for the first page. |
order | string | "desc" | "asc" or "desc" by createdAt. |
Response wrapper:
{
"data": [ ... ],
"nextCursor": "cur_abc123",
"hasMore": true,
"total": 847
}
nextCursor is null when there are no further pages. total is the count of all matching records ignoring pagination — may be omitted on high-cardinality lists for performance. Always use hasMore to determine whether to continue paginating, not total.
Idempotency
The publish and install endpoints support idempotency keys to safely retry on network failure.
Pass an Idempotency-Key header with any string unique to the attempt (a UUID is recommended):
POST /envelope/v1/installs
Idempotency-Key: 7f3d9a2c-1b4e-4f8a-9c0d-2e5f7a8b3c6d
If the registry receives two requests with the same idempotency key within 24 hours:
- If the first request succeeded: return the original response with
HTTP 200and headerIdempotency-Replayed: true - If the first request is still in progress: return
HTTP 409with codeIDEMPOTENCY_REQUEST_IN_PROGRESS - If the first request failed: the key is invalidated; a new attempt may use the same key
Idempotency keys expire after 24 hours. After expiry, a request with the same key is treated as a new request.
Rate limits
Registry API
| Tier | Limit | Scope |
|---|---|---|
| Unauthenticated | 60 requests/min | Per IP |
| Free plan | 300 requests/min | Per API key |
| Pro plan | 1,000 requests/min | Per API key |
| Scale plan | 5,000 requests/min | Per API key |
Publish operations (POST to versions) are additionally limited to 60 publishes/hour per owner regardless of plan, to prevent spam.
Runtime API (run invocations)
Run invocation rate limits are set by the deployer's plan and are separate from registry limits.
| Plan | Run limit | Token limit |
|---|---|---|
| Free | 500 runs/month | 1M tokens/month |
| Pro | 10,000 runs/month | 25M tokens/month |
| Scale | Unlimited | Unlimited (fair use) |
Limits reset on the first day of each billing period. Current usage is returned in response headers on every run endpoint (see quota response headers).
Exceeded limits
When a rate limit is exceeded, the API responds with HTTP 429 and:
Retry-Afterheader: seconds until the limit resetsX-Envelope-Limit-Type:"rate"(request rate) or"quota"(billing period quota)
CLI reference
The @openenvelope/cli package provides the envelope command. Install globally or use via npx. (Coming soon — not yet published.)
npm install -g @openenvelope/cli
# or
npx envelope <command>
Commands
envelope init # Scaffold a new .envelope.json in the current directory
envelope validate [file] # Validate a team definition against the schema. Default: ./team.envelope.json
envelope publish [file] # Publish a new version to the registry
envelope whoami # Show the authenticated user and org for the current API key
envelope teams list # List all teams owned by the authenticated user or org
envelope teams get <slug> # Show full team definition for a slug
envelope versions list <slug> # List all versions for a team
envelope installs list # List active installs for the authenticated deployer
envelope installs health <id> # Check health of an install
envelope run <installId> [input] # Invoke an install synchronously. input is JSON string or @file.json
envelope logs <installId> # Stream recent run events for an install
envelope conform # Run the conformance test suite against a runtime (see conformance)
Authentication
envelope login # Opens browser to authenticate and stores key in ~/.envelope/config
envelope logout # Removes stored credentials
envelope config set key <value> # Set config values (e.g. default org slug)
The CLI reads ENVELOPE_API_KEY from the environment if set, overriding stored credentials. This is the recommended approach for CI/CD.
Schema evolution and support policy
Versioning
The Envelope schema itself is versioned separately from team definition versions. The schema version appears in the $schema URL:
https://schema.openenvelope.org/team/v1.json ← schema v1
https://schema.openenvelope.org/team/v2.json ← schema v2 (future)
Backwards compatibility within a major version
Within a schema major version (v1), Envelope commits to:
- Never removing a field that was previously valid
- Never changing the type of an existing field
- Never making an optional field required
- Additions only (new optional fields, new valid enum values)
Major version upgrades
When a new major schema version is released:
- A migration guide is published at
schema.openenvelope.org/migrate/v1-to-v2 - The previous major version enters maintenance mode: security fixes only, no new features
- The previous major version is supported for a minimum of 18 months after the new major is declared stable
- Team definitions referencing a deprecated schema version continue to function but display a migration notice in the registry
- Builders are not required to migrate. Deployers installing a team on a deprecated schema version receive a warning
Changelog
All schema changes are documented in the CHANGELOG at github.com/openenvelope/schema/CHANGELOG.md. Changes are tagged with: added, changed, deprecated, removed, fixed, security.
Teams of teams (v2 concept)
The current schema (v1) only supports agents as the atomic unit within a team. A planned v2 capability is team composition — the ability to reference a published team as a sub-team within a parent team definition.
{
"key": "research-team",
"type": "team",
"teamRef": "acme/deep-research-team@^2.0.0",
"reportsToKey": "manager-agent"
}
This would allow builders to compose complex multi-team systems from published, versioned primitives — the same way npm packages depend on other npm packages.
Design considerations flagged for v2:
- Circular dependency detection (a team cannot reference itself, directly or transitively)
- Billing: how are run costs attributed across nested team boundaries?
- Secrets: how are secret namespaces isolated between a parent team and its sub-teams?
- Versioning: semver range syntax (
^2.0.0) vs pinned version (2.1.3) — tradeoffs in stability vs reproducibility - Governance: if a sub-team is updated by its builder, does the parent team automatically receive the update or does it require explicit re-pinning?
Teams of teams is not in scope for v1. This section is flagged as forward-looking design intent.
Security and isolation
Secret isolation between installs
Each install receives its own set of secret values. The runtime must ensure strict isolation: a secret supplied for ins_a1b2c3d4 must never be readable, injectable, or inferable from within ins_xyz99999, even if both installs are running the same team version on the same runtime infrastructure.
Envelope-certified runtimes must document their secret isolation mechanism (e.g. separate secret stores, per-install encryption keys, namespace isolation) as part of the conformance declaration. This documentation is surfaced to deployers in the Envelope registry.
Secret transmission
Secrets in InstallRequest.secrets are encrypted at the registry before transmission to the runtime. The encryption uses AES-256-GCM with a key established during the certification handshake. The runtime decrypts using this key. The shared key is rotated annually or on request.
Plain-text secret values must never appear in:
- Run event
inputs,outputs, orerrorfields - Runtime logs
- Error messages returned to the registry or deployer
- Any API response
Multi-tenant agent isolation
Agents running on shared infrastructure must be isolated at the process or container level — not just at the application layer. A bug or prompt injection attack in one agent must not be able to read memory, files, or environment variables from another agent or install.
Prompt injection
Builders are responsible for writing prompts that are robust to prompt injection from end-user inputs. Envelope does not automatically sanitise input values. The accessPolicy field is the primary defence mechanism for limiting the blast radius of a successful injection.
Audit trail
Runtimes must write an audit record for every run, including:
installId,runId,startedAt,completedAt- Token counts per agent
- Any access policy decisions (allowed and denied)
- Whether secrets were accessed during the run
Audit records must be retained for a minimum of 90 days and be accessible to the deployer via the run event API.
Content policy and reserved slugs
Content policy
Teams published to the Envelope registry must not:
- Perform, automate, or facilitate illegal activity
- Deceive users about the nature of the system (e.g. claiming to be human)
- Generate, distribute, or process CSAM or other illegal content
- Execute destructive operations without explicit user confirmation (irreversible deletes, bulk data wipes)
- Scrape or exfiltrate data from third-party systems without authorisation
- Impersonate other teams, builders, or organisations
Teams in violation are removed without prior notice. Repeat violations result in account suspension. Builders may appeal via [email protected].
Reserved slugs
The following slugs cannot be registered by any builder:
admin, api, app, assets, auth, billing, blog, cdn, cli, connect, console, dashboard, docs, download, enterprise, envelope, featured, governance, health, help, hooks, installs, internal, legal, login, logout, me, metrics, new, observability, onboarding, orgs, pricing, privacy, public, registry, runs, schema, search, security, settings, signup, slack, status, support, teams, terms, verify, webhooks, well-known
Slugs that closely resemble official Envelope properties (envelope-official, envelope-support) are also reserved and will be rejected.
SDK and client library
@openenvelope/schema
TypeScript types, JSON Schema, and a pre-compiled Ajv validator. Available now on npm.
npm install @openenvelope/schema
import { validate, TeamDefinition, AgentDefinition } from '@openenvelope/schema';
import schema from '@openenvelope/schema/schema.json' assert { type: 'json' };
// Using the built-in validator (recommended)
const result = validate(myTeamDefinition);
if (!result.valid) console.error(result.errors);
// Or compile your own with the bundled schema
import Ajv from 'ajv';
const ajv = new Ajv();
const myValidate = ajv.compile(schema);
const valid = myValidate(myTeamDefinition);
@openenvelope/client (coming soon)
A typed HTTP client for interacting with the Envelope registry and runtime APIs from application code.
npm install @openenvelope/client
import { EnvelopeClient } from '@openenvelope/client';
const client = new EnvelopeClient({
apiKey: process.env.ENVELOPE_API_KEY, // builder key
deployerToken: process.env.ENVELOPE_DEP_TOKEN // deployer token
});
// Invoke a run
const result = await client.run('ins_a1b2c3d4', {
inputs: { ticket_text: 'My order is delayed.' }
});
// Async with polling
const run = await client.runAsync('ins_a1b2c3d4', { inputs: { ... } });
const result = await run.wait(); // polls until complete
// Streaming
const stream = await client.runStream('ins_a1b2c3d4', { inputs: { ... } });
for await (const event of stream) {
console.log(event.type, event);
}
CORS
The registry API sets the following CORS headers to support browser-based invocations:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type, Idempotency-Key
Run invocation endpoints on the runtime API must also support CORS if deployers intend to invoke teams directly from browser applications. Runtimes may restrict Access-Control-Allow-Origin to deployer-registered domains for security.
Conformance test suite
The @openenvelope/conform package tests whether a runtime fully implements the Envelope schema. Run it against any runtime that exposes the required API surface. (Coming soon — not yet published.)
npx @openenvelope/conform --runtime https://your-runtime.example.com --token <cert-token>
Test categories
Install lifecycle (required):
- Provision a valid team definition — expect
status: "active"and all agents present inprovisionedAgents - Provision with missing required secret — expect
status: "failed"and codeMISSING_SECRET - Fetch install status — expect correct
provisionedAgentsandstatus - Delete install — expect
204and subsequentGETto return404
Secret handling (required):
- Rotate a secret post-install — expect new value used in subsequent run
- Verify old secret value is not returned in any API response (redaction check)
- Verify secrets from install A are not accessible in install B (isolation check)
Run invocation (required):
- Synchronous run with valid inputs — expect
status: "completed"andoutputsmatching schema - Run with invalid inputs — expect
INVALID_INPUTerror - Async run — expect
202, poll untilcompleted - Run cancellation — expect
status: "cancelled"afterDELETE - Quota headers present on every run response
Timeout handling (required):
- Run that exceeds
timeout.runMs— expectRUN_TIMEOUTerror - Individual agent that exceeds
timeout.agentMs— expectAGENT_TIMEOUTerror
Access policy (required for http adapter):
- Agent attempts blocked host — expect run to fail with
ACCESS_POLICY_VIOLATION - Agent attempts allowed host — expect run to succeed
Health check (required):
- Healthy install returns
status: "healthy"for all agents - Degraded runtime condition surfaced in agent-level status
Streaming (optional — declare in conformance profile):
- SSE stream returns
agent.start,agent.token, andrun.completeevents in order stream: trueon non-streaming runtime returnsSTREAMING_NOT_SUPPORTED
Output validation (optional — declare in conformance profile):
- Output that violates declared schema returns
OUTPUT_SCHEMA_VIOLATION
Conformance profile
Runtimes declare which optional capabilities they support in a conformance profile returned by GET /envelope/v1/health:
{
"status": "ok",
"envelopeSchemaVersion": "1",
"capabilities": {
"streaming": true,
"outputValidation": true,
"localAdapters": ["claude_local", "codex_local"]
}
}
The conformance profile is displayed on the runtime's listing in the Envelope registry, allowing deployers to filter by capability.
Ecosystem this enables
Once the schema is public and stable:
- IDE extensions — VS Code, Cursor, JetBrains plugins with schema-aware autocomplete
- Visual editors — drag-and-drop team builders that export valid schema JSON
- Third-party validators — integrated into popular CI/CD platforms
- Converters — tools that translate from other agent definition formats into Envelope schema
- Runtime adapters — runtimes beyond Paperclip that can read and execute Envelope team definitions, enabling true platform agnosticism
- Community archetypes — published, reusable agent role templates that builders compose from
- Observability integrations — run events streamed to Datadog, Grafana, or any tool that can ingest the run event schema
Driving platform adoption
Open-sourcing the schema is how Envelope becomes the standard rather than just another connector. The mechanism:
Certification model — platforms that fully implement the schema and pass a conformance test suite get listed as Envelope-native on the registry. Deployers can filter teams by supported platform. Being listed is a distribution incentive for platforms to implement the spec rather than wait for Envelope to build a custom integration for them.
Conformance requirements:
- Implement all required API surface endpoints (see above)
- Accept a valid Envelope Team Definition via
POST /envelope/v1/installs - Provision agents preserving name, title, role, and reporting relationships
- Surface
requiredSecretsandrequiredVariablesto the deployer during setup - Return a structured InstallResult including provisioned agent IDs and a dashboard URL
- Emit run events in the standard run event schema
- Pass the published conformance test suite (
npx @openenvelope/conform)
The flywheel:
More platforms implement the schema
↓
More teams are instantly deployable everywhere
↓
Envelope registry becomes more valuable to creators
↓
More creators publish teams
↓
More deployers come to Envelope to find teams
↓
More platforms want to be listed as Envelope-native
↓ (repeat)
The disintermediation risk — a large platform implements the schema, builds their own registry on top of it, and cuts Envelope out. Mitigations:
- The schema alone is not the product — the registry, creator tools, and payment layer are
- Creator reputation, review history, and usage data don't transfer to a copy
- A platform that implements the schema still sends deployers to Envelope to find teams
- First-mover advantage on the spec means Envelope defines what valid looks like
Relationship to platform agnosticism
The open schema is the technical foundation for platform agnosticism. Right now Envelope team definitions are built with Paperclip in mind. Publishing the schema as an open standard means any orchestration runtime can implement support for it. Builders define once; the schema works wherever a compatible runtime exists. Envelope remains the distribution layer — the registry where teams are discovered, deployed, and monetised — regardless of which runtime executes them.
See multi-orchestrator.md for the phased integration plan covering the short-term serialiser approach and how it gives way to native schema adoption over time.
Distribution
- GitHub:
openenvelope/schema— public repo, versioned releases, community issues and PRs - npm:
@openenvelope/schema— TypeScript types, JSON Schema, and pre-compiled validator for use in any project - Docs:
schema.openenvelope.org— reference documentation, migration guides between versions, contribution guide
Version history
| Version | Status | Notes |
|---|---|---|
| v1.2.0 | Unreleased | Adds persistent workspace documents with typed columns, ownership, PII flags, status values, condition triggers, and forward-compatible hypotheses |
| v1.1.0 | Current | Added event, condition, continuous trigger types; webhookSecret, conditionPrompt, conditionInterval schedule fields; metadata.generatedBy at team level; triggerSource on run events; _envelope attribution field on run responses |
| v1.0.0 | Maintenance | Initial public release |
Frequently asked questions
Is the schema backwards compatible? Minor versions (v1.x) are backwards compatible — existing multi-agent team specs continue to work as new fields are added. Breaking changes require a major version bump. See the schema versioning policy for the full stability guarantees.
Can I modify the schema for my own use? Yes — the schema is Apache 2.0. Fork it, extend it, use it however you like. Fields not in the standard are ignored by conforming runtimes; your own tooling can use them freely.
Does Envelope validate my design automatically?
Yes — the workspace validates your multi-agent workflow design against the schema as you build. You can also validate any .envelope.json file locally using any JSON Schema validator pointed at schema.openenvelope.org/team/v1.json.
Why open-source the schema? A shared open schema means any runtime can implement support — not just Envelope's hosted platform. It creates a portable standard for multi-agent workflow definitions, similar to how OpenAPI standardised REST APIs. Teams designed in Envelope can be implemented on any conforming runtime.
→ Why open standards matter for AI infrastructure — the strategic argument for open multi-agent workflow formats
Schema versioning policy
How the schema evolves, what counts as a breaking change, and platform implementor obligations.