Skip to content

Manifest Reference

Every component package contains two files in a warmhub/ directory:

  • component.json — Component identity (id, name, version)
  • manifest.json — Declarative resource definitions

Both are validated at install time. You can also validate offline with wh component validate <path>.

Add the $schema field to get IDE autocomplete and inline validation:

{
"$schema": "https://docs.warmhub.ai/schema/component-manifest.v1.json",
"component": { ... },
...
}

Published schema versions are immutable — breaking changes always move to a new versioned URL (e.g. component-manifest.v2.json), so it is safe to pin the URL above.

{
"id": "com.warmhub.MyComponent",
"name": "my-component",
"version": "1.0.0",
"description": "Optional description",
"author": "Your Name",
"tags": ["research", "ai"]
}
FieldTypeRequiredDescription
idstringYesReverse-DNS identifier (e.g., com.warmhub.MyComponent); manifest metadata, not a CLI argument.
namestringYesInstalled component name, typically kebab-case. Must be unique within the target repo — the installer rejects an install when another component already uses the same name. This is the name CLI invocation resolves against: wh component exec <name> <method> (and the wh <name> <method> shorthand) look up the install by this value, so it is not merely a display label.
versionstringYesSemver version string.
descriptionstringNoHuman-readable description.
authorstringNoAuthor or organization.
tagsstring[]NoDiscovery tags.

The id must match the pattern: one or more lowercase domain segments, followed by one or more PascalCase segments. Examples: com.warmhub.ResearchKnowledge, io.example.MyTool.

Every manifest needs a component object (id, name, version) plus the shapes, credentials, subscriptions, seeds, health, and teardown sections — those six may be empty arrays or objects. $schema, runtimeAccess, and cli are optional.

Earlier manifests also had an actions section. It has been removed from the manifest format: an actions key in an older manifest is ignored — install and reconcile never read it.

{
"$schema": "https://docs.warmhub.ai/schema/component-manifest.v1.json",
"component": {
"id": "com.warmhub.MyComponent",
"name": "my-component",
"version": "1.0.0"
},
"shapes": [],
"credentials": [],
"subscriptions": [],
"seeds": [],
"health": {},
"teardown": {}
}

The component section must match the corresponding fields in component.json.

Declare the WarmHub runtime scopes the component’s minted runtime token needs at execution time. This is used only for registered-component setup flows that mint tokens; local installs can still include the field for portability.

"runtimeAccess": {
"reads": ["Paper", "ComponentConfig"],
"writes": ["Consensus"]
}
FieldTypeRequiredDescription
readsstring[]YesShape names the runtime token may read.
writesstring[]YesShape names the runtime token may write.

Rules:

  • Every shape must be declared in shapes[], be a platform built-in (such as LicenseSubject or LicenseDeclaration), or be an install-infrastructure shape (ComponentInstall, ComponentConfig).
  • A shape may appear in both reads and writes when the runtime needs read-before-write behavior, such as revising existing component-owned assertions.
  • Omitting runtimeAccess is equivalent to no minted runtime token.

shapes, credentials, and subscriptions support a per-resource provisioning field:

  • manifest — default. The normal manifest installer creates the resource in the target repo.
  • setup — the registered component’s setup endpoint creates the resource. When minted tokens are enabled, WarmHub includes a setup token the endpoint can use for those writes.

This allows a manifest to mix installer-created and setup-created resources in one package.

Platform built-in shapes are excluded from both creation paths and from setup-token scopes, including reads and updates. Declarations must match the platform fields and composition or publish rejects them. A matching declaration is satisfied by the platform; conflicting or retracted stored rows reject the install and roll back the resource-phase writes. LicenseDeclaration must declare composes: ["Assertion"]. Reserved Collection and retired Triple cannot be declared. Declaring a built-in never adds a shape-create scope or a shapes:write requirement.

A minted setup token does more than authorize the initial create-time writes. Its scope also covers:

  • Scoped reads on setup-owned resources: the token may read any Subscription/<name>, CredentialSet/<name>, or non-built-in shape declared with provisioning: "setup" in the current manifest. Name shapes by their bare name (e.g. Paper), and subscriptions and credential sets by their typed form (e.g. Subscription/rk-on-paper-add, CredentialSet/github-creds). The token cannot read resources outside those names.

  • Credential binding between setup-owned resources: the token may bind a setup-owned CredentialSet/<name> onto a setup-owned Subscription/<name>, provided the subscription’s manifest declaration lists that credential set in its credentials array. Both refs must be within the token’s setup-owned scope, and a credential set that is declared in the manifest but not listed by the target subscription’s credentials array will be rejected with an error. This lets your setup endpoint wire credentials to subscriptions during install or update without requiring any broader token.

  • Update paths on those same named resources: wh component update replays registered setup against existing setup-provisioned resources. During a replay, the setup token may update a matching subscription (e.g. change its webhookUrl), set keys on a matching credential set, and reconcile a matching shape — without requiring a full teardown and recreate.

    Replay validation: a setup replay may only touch setup-owned resources that still match the current manifest. Anything that disagrees with the manifest is rejected before it persists, so write your setup endpoint to submit what the manifest declares rather than what it remembers from a previous run. Specifically:

    • Shapes must match the manifest declaration exactly — the field map and the description, if one is declared. Changing or dropping a declared description fails even with the fields untouched.

    • Subscriptions must keep the declared event type, shape, and filter criteria. Webhook URLs have their own rules:

      • The origin must match; everything after it — path, query string — may change. Origin here means scheme, host and port, so https://hooks.example.com/a and https://hooks.example.com/b?v=2 are interchangeable, while a different host, or the same host on a different port (https://hooks.example.com:8443/a), is rejected.
      • A {setup-supplied:...} placeholder accepts any concrete URL whose path fits its template.
      • fallbackWebhookUrl follows the same origin rule, and its presence must match the manifest — adding or removing a fallback is rejected even when the primary URL is untouched.

      Two things a setup-managed subscription may never do, regardless of the manifest: point at another repository as its source (sourceRepoRef), or enable allowTraceReentry. Both are rejected even when everything else matches.

    • Credential sets must keep their name and scope, and you may only set key names listed in requiredKeys.

    • Credential bindings require both resources to be declared in the manifest, with the subscription’s declaration listing that credential set in its credentials array.

In practice this means your setup endpoint can be written idempotently: on a first run it creates the subscriptions, credential sets, and shapes, and binds credentials to subscriptions; on a subsequent wh component update replay it reads the existing resources, compares them to the desired state, and reconciles in place. Each install or update run issues a fresh setup token scoped to the current manifest. What happens after a failure depends on whether WarmHub had already called you:

  • Failed before the call reached you — safe to retry. Do not count on holding a usable token in the meantime: a dispatch failure revokes the tokens minted for that run, so on an update you fall back to the live tokens from the existing install, and on a first install there may be none yet. Take the token from the next run rather than caching one across attempts.
  • Failed after the call may have reached you — retry is blocked rather than replayed, because your endpoint may already have acted. Recovering means reconciling your side by hand, then re-running deliberately.

The token’s scope is derived from the latest published manifest resolved for the current install or update run — not from the names that were provisioned during the original install. This means that if you rename a setup-owned resource or add a new one between releases, the replay token for wh component update is bounded to the current manifest’s setup-owned names, and the updated or newly added resources are part of the replay scope.

Shapes the component needs in the target repo. Created at install time if they don’t exist.

"shapes": [
{
"name": "Paper",
"fields": { "title": "string", "url": "string", "score": "number" }
}
]
FieldTypeRequiredDescription
namestringYesShape name.
fieldsobjectYesField definitions mapping names to WarmHub type descriptors.
descriptionstringNoHuman-readable description of the shape.
provisioning"manifest" | "setup"NoWhich side creates an ordinary shape. Defaults to manifest; platform built-ins are excluded from both creation paths.
composesstring[]NoShape references whose fields this shape inherits. Each target must resolve when the shape is committed. Naming a platform built-in here does not materialize it; the reference must resolve to an existing shape. A platform built-in declaration must use exactly the platform composition, including LicenseDeclaration → Assertion.

Publish validation checks the composition list’s syntax; target resolution happens in the destination repo during the shape commit. A successful publish does not establish that composition targets exist there.

Field types: string, number, boolean, wref (a WarmHub reference string such as cave, cave@v3, or hub/shapes/Location), arrays, nested objects. See Shapes for details.

Credential sets the component needs for external API access. With the default provisioning: "manifest", the installer creates the set and the user populates its required keys afterward. With provisioning: "setup", the registered component’s setup endpoint creates and fills the set.

"credentials": [
{
"name": "github-creds-<org-name>-<repo-name>",
"description": "GitHub API access for fetching repos",
"provisioning": "manifest",
"requiredKeys": [
{ "key": "github_token", "description": "Personal access token with repo scope" }
]
}
]
FieldTypeRequiredDescription
namestringYesCredential set name.
descriptionstringNoWhat these credentials are for.
requiredKeysarrayYesKeys that must be set.
provisioning"manifest" | "setup"NoWhich side creates the credential set. Defaults to manifest.
requiredKeys[].keystringYesKey name.
requiredKeys[].descriptionstringNoWhat the key is used for.

Credential names may include the template tokens:

  • <org-name>
  • <repo-name>

These are resolved at install/setup time. For example, veritas-webhook-<org-name>-<repo-name> becomes veritas-webhook-acme-world.

After install, populate keys with:

Terminal window
wh credential set github-creds-acme-world github_token --value ghp_... --repo acme/world

Subscriptions that trigger webhooks on write events.

"subscriptions": [
{
"name": "rk/on-paper-add",
"trigger": { "kind": "event", "shape": "Paper" },
"kind": "webhook",
"webhookUrl": "https://handler.example.com/on-paper",
"credentials": ["github-creds-<org-name>-<repo-name>"]
}
]
FieldTypeRequiredDescription
namestringYesSubscription name. Convention: <prefix>/<description>.
triggerobjectYesWhat fires the subscription.
kind"webhook"NoOptional advisory field retained for readability. The CLI derives the effective subscription kind from trigger.kind and ignores this field at install time.
webhookUrlstringYesDestination URL for deliveries.
credentialsstring[]NoCredential sets to bind. Currently limited to one.
fallbackWebhookUrlstringNoOptional fallback delivery target.
provisioning"manifest" | "setup"NoWhich side creates the subscription. Defaults to manifest.

Event trigger:

FieldTypeRequiredDescription
kind"event"Yes
shapestringYesShape to watch for changes. Must be declared in shapes[] or be a subscription-trigger built-in (ComponentInstall, ComponentConfig); validation rejects an undeclared shape.
filterobjectNoAdditional filter criteria.

Initial data created at install time.

"seeds": [
{
"kind": "thing",
"shape": "ComponentConfig",
"name": "my-component",
"data": { "version": "1.0.0", "enabled": true }
}
]
FieldTypeRequiredDescription
kind"thing"YesCurrently only "thing" is supported.
shapestringYesShape name. Must be declared in shapes, be a platform built-in, or be an install-infrastructure shape (ComponentInstall, ComponentConfig).
namestringYesThing name, unique within the repository regardless of Shape.
dataobjectYesField values. Must conform to the shape’s field definitions.

Platform built-ins and install-infrastructure shapes need no shapes declaration for seeds. Seed data must still conform to the target shape’s field definitions.

Pair cannot be written as a component Thing seed and is rejected during manifest validation, even if declared in shapes.

Extra resources wh component doctor must find for the component to be healthy — the ones it depends on but does not itself declare.

"health": {
"requires": {
"shapes": ["Paper", "PaperSummary"],
"things": ["my-component"],
"subscriptions": ["rk/on-paper-add"]
}
}
FieldTypeDescription
requires.shapesstring[]Shape names doctor probes for existence and active status.
requires.thingsstring[]Literal Thing names doctor probes for existence, for example my-component when that is the seed’s name. A slash is part of the name if present; this field does not resolve Shape/name as a shape-qualified reference.
requires.subscriptionsstring[]Subscription names doctor probes for existence and active status.

All fields are optional. wh component doctor probes each name alongside the shapes, subscriptions, credentials, and seeds declared elsewhere in the manifest, and a missing or unreadable required resource makes the component degraded (or error) exactly as a missing declared one does.

Publish rejects more than 1,000 distinct doctor resource identities across declarations and health.requires; doctor applies the same limit before resource probes. Repeated names within one resource family count once. Credential templates count before substitution, even if they resolve to the same set in a target repo.

A name that the manifest already declares is probed once and keeps its ordinary shape:, sub:, or seed: finding — listing your own declared resources here changes nothing. A name that appears only under requires gets its own finding under require:shape:, require:sub:, or require:thing:, and is checked for existence plus active status for shapes and subscriptions. Doctor has no field definition of its own to compare against, so it reports no drift or ownership findings for required-only resources. An inactive required shape or subscription makes the component degraded.

A required shape that is a platform built-in may be absent because the platform materializes it lazily on first write. Doctor checks any existing row for a conflicting definition; a conflict degrades health, while a failed lookup reports an error. Built-in ownership does not gate health.

For a conflicting active built-in shape with different fields, uninstall its owning legacy component first if it has one, then rename the shape and retry. An old LicenseDeclaration without captured Assertion composition, or a retracted built-in, needs administrator repair; ordinary rename does not resolve it.

wh component teardown performs a terminal backend uninstall: it pauses subscriptions using subscriptions.onDisable, releases owned shapes, revokes component tokens, dispatches an uninstall callback when configured, and marks the install uninstalled. onUninstall is accepted by the schema but is not used by the current implementation.

"teardown": {
"subscriptions": {
"onDisable": "pause",
"onUninstall": "delete"
}
}
FieldTypeDescription
subscriptions.onDisable"pause"Subscription policy used by component teardown.
subscriptions.onUninstall"pause" | "delete"Accepted by the schema; unused by the current uninstall implementation.

Optional. Declares a CLI surface the component exposes to operators. When present, the listed methods become invokable through wh component exec <component> <method> (or the shorthand wh <component> <method>) once the component is installed. Omit the section if the component has no operator-facing commands.

The example below is the get-reputation method from the shipping Veritas manifest:

"cli": {
"description": "Read and adjust Veritas source reputations from the install repo.",
"methods": [
{
"name": "get-reputation",
"description": "Get a single Veritas source reputation by durable ID and scope.",
"credentialSet": "veritas-webhook-<org-name>-<repo-name>",
"method": "GET",
"path": "reputation",
"requiresPermission": "repo:read",
"args": [
{ "name": "durable-id", "type": "string", "required": true, "description": "Source Thing durable ID whose reputation to read. Do not pass a label wref." },
{ "name": "scope", "type": "string", "required": true, "description": "Scope the reputation is held under." }
]
}
]
}
FieldTypeRequiredDescription
descriptionstringNoOne-line summary of the CLI surface, shown in wh --help.
methodsarrayYesThe invokable methods. May be empty.
methods[].namestringYesMethod name (lowercase kebab, unique within methods).
methods[].descriptionstringNoWhat the method does.
methods[].credentialSetstringYesName of an entry in this manifest’s credentials[] that holds the method’s auth. The referenced set must declare a supported CLI auth scheme.
methods[].argsarrayYesDeclared arguments (see below). May be empty.
methods[].method"GET" | "POST" | "PUT" | "PATCH" | "DELETE"NoDefaults to POST. Use GET for read-only lookups.
methods[].pathstringNoURL path the method routes to, decoupling the route from the method name. One or more lowercase-kebab segments joined by / (e.g. reputations/get). Defaults to the method name. Two methods may share a path only if their method (verb) differs.
methods[].requiresPermission"repo:read" | "repo:checkpoint-read" | "repo:checkpoint-generate" | "repo:write" | "repo:configure" | "repo:admin" | "repo:action-callback"NoThe repo-level scope the operator must hold on the install repo to call this method. Defaults to repo:read. See Method permissions for how the scopes combine and what they do not cover.
methods[].args[].namestringYesArg name (lowercase kebab, unique within the method).
methods[].args[].type"string" | "integer" | "number" | "boolean"YesArg type.
methods[].args[].requiredbooleanNoWhether the arg must be supplied.
methods[].args[].defaultstring | number | booleanNoDefault value used when the arg is omitted. Must match the arg’s type. For integer/number args it must fall within any declared min/max. For a string arg with a pattern, it must match that pattern and be at most 65,536 characters (UTF-16 code units).
methods[].args[].min / maxnumberNoNumeric bounds for integer/number args.
methods[].args[].patternstringNoRE2 regex the string arg’s full value must match — it is anchored to the whole value, so a partial match never succeeds. Must be at most 500 characters (UTF-16 code units) and use RE2-supported syntax only; JavaScript-only constructs such as lookaheads and backreferences are not supported. When a pattern is set, wh component exec rejects a supplied value longer than 65,536 characters (UTF-16 code units) before the pattern is checked.
methods[].args[].descriptionstringNoWhat the arg is for.

When an arg declaration is invalid, wh reports the error against the offending argument and names the field that broke the rule — for example its name, type, min/max, pattern, or default. Fix the flagged field and re-validate with wh component validate.

methods[].requiresPermission names the repo scope a caller must hold on the install repo before the method runs:

  • Every method requires repo:read at minimum. Setting a higher value — repo:write, for example — adds that scope on top of repo:read.
  • Use repo:checkpoint-read for methods that read existing portable checkpoints, repo:checkpoint-generate for methods that create or trigger generation of a new checkpoint, and repo:action-callback for repo-scoped action-run callback delivery.
  • The field is a coarse repo-scope gate only. Any finer-grained authorization — object-level or action-level checks, for instance — remains the component service’s responsibility to enforce in its handler, using the caller identity and context it is given.

When methods is non-empty, runtimeAccess.writes must include ComponentConfig. Without it, the component’s CLI methods won’t be callable after install.

{
"$schema": "https://docs.warmhub.ai/schema/component-manifest.v1.json",
"component": {
"id": "com.warmhub.E2eEcho",
"name": "e2e-echo",
"version": "1.0.0"
},
"shapes": [
{ "name": "EchoInput", "fields": { "message": "string", "priority": "number" } },
{ "name": "EchoOutput", "fields": { "echo": "string", "processedAt": "string" } }
],
"credentials": [
{
"name": "echo-api-creds",
"description": "API credentials for echo service",
"requiredKeys": [{ "key": "api_key", "description": "Echo API key" }]
}
],
"subscriptions": [
{
"name": "echo/process-input",
"trigger": { "kind": "event", "shape": "EchoInput" },
"kind": "webhook",
"webhookUrl": "https://echo.example.com/process",
"credentials": ["echo-api-creds"]
}
],
"seeds": [
{
"kind": "thing",
"shape": "EchoInput",
"name": "welcome",
"data": { "message": "Hello from e2e-echo component", "priority": 1 }
}
],
"health": {},
"teardown": { "subscriptions": { "onDisable": "pause" } }
}

Run offline validation before installing:

Terminal window
wh component validate ./my-component

This checks:

  • JSON syntax and required fields
  • Cross-references (subscriptions reference valid credentials, seeds reference valid shapes)
  • Duplicate names within sections
  • Subscription trigger and webhook validation
  • Component ID format (reverse-DNS)
  • Consistency between component.json and manifest.json