Manifest Schemas
Reference for every manifest/payload file format used by installable
catalog artifacts: MANIFEST.toml (package metadata, every package type),
the per-type payload file (SKILL.md, COMPETENCY.md, AGENT.md,
workflow.json, mcp.json/adapter.json), and COLLECTION.toml.
Two layers
Every package is MANIFEST.toml (identity/dependencies/standards) plus a
payload/ directory containing exactly one payload file, whose name is
fixed by [package] type:
type | Payload file | Contains |
|---|---|---|
skill | payload/SKILL.md | YAML frontmatter + markdown body (tool config / prompt context) |
competency | payload/COMPETENCY.md | YAML frontmatter + markdown body (system prompt) |
agent-template | payload/AGENT.md | YAML frontmatter + markdown body (system prompt) |
workflow-template | payload/workflow.json | Native JSON — a DAG of steps, not a document |
integration | payload/mcp.json (or payload/adapter.json for a REST/SOAP contract, ADR-057) | Native JSON |
contract | payload/CONTRACT.json | Native JSON — a schema-bearing tool-surface role (ADR-066) |
There is no tool package type — packages of type skill cover both
single-tool and multi-tool bundles. The full type enum is exactly: skill,
competency, agent-template, workflow-template, integration,
contract.
Package identity (name) lives only in MANIFEST.toml [package].
None of the payload frontmatter formats carry id/name fields —
MANIFEST.toml [package] name is the sole identity other packages
reference via skills/integrations/competencies/dependency keys.
Author attribution for a package also does not live in MANIFEST.toml
— there is no [package.author] section on the real schema. Package
attribution is the authenticated publisher_id/publisher_role the
registry records at publish time, not self-declared text (issue #619).
COLLECTION.toml is the one place a free-text [author] is still valid —
collections are an admin-curated resource, a lower-stakes context.
MANIFEST.toml (Package Metadata)
[package]
type = "competency" # skill | competency | agent-template | workflow-template | integration
name = "claims-intake" # kebab-case, 3-64 chars — the package's sole identity
title = "Claims Intake" # required, human-readable display name (non-blank)
version = "1.2.0" # semver 2.0 (MAJOR.MINOR.PATCH, optional pre-release)
description = "FNOL processing" # required, ≤500 chars
license = "Apache-2.0" # SPDX identifier (non-empty, no newlines/tabs)
min_platform_version = "0.5.0" # required, valid semver
[package.metadata]
repository = "https://github.com/..." # optional, must be http(s)
homepage = "https://..." # optional, must be http(s)
keywords = ["insurance", "claims"] # max 20 entries, each ≤50 chars
categories = ["insurance/claims"] # taxonomy paths (no format validation beyond being strings)
regions = ["ZA", "US"] # ISO 3166-1 alpha-2 codes (empty = all regions); case-insensitive on input, normalized upper-case
[dependencies]
document-ocr = "^1.0" # semver constraint. The catalog resolver ONLY ever
postgresql-connector = ">=2.0.0, <3.0.0" # walks [dependencies] -- there is no optional/peer
# dependency concept; every payload ref this manifest
# can carry (skills/integrations/competencies/
# agents_required) is mandatory at equip time
[standards]
mcp_compatible = true # only valid for skill/integration package types
openapi_spec = "payload/openapi.yaml" # only valid for skill/integration package types
[signing]
content_hash = "sha256:a1b2c3d4..." # populated by `hoziron-cli package build` — SHA-256
# Merkle root over payload/. Provenance (publisher
# identity, registry signature) is added by the
# registry at publish time, stored in the package
# index -- not in the archive itself, and not a
# MANIFEST.toml field
[implements.claims-write] # only for integration packages (ADR-066).
version = "1.0.0" # Declares this integration satisfies the
# "claims-write" contract package at the
# given version.
[implements.claims-write.tool_map] # contract tool name -> this integration's
create_claim = "create_fnol" # native tool name. Omit an entry when the
# native name already matches the contract's.
[implements.claims-write.field_map.create_fnol] # per-tool: contract field name ->
policy_ref = "policy_number" # native field name. Omit a tool's
# entry, or a field within it, when
# the native name already matches.
Package types
| Type | Payload | Use case |
|---|---|---|
skill | SKILL.md | Tool bundle + runtime config (covers single- and multi-tool packages) |
competency | COMPETENCY.md | Agent behavior definition — system prompt + equipped skills |
agent-template | AGENT.md | Pre-configured agent (competencies + tool visibility) |
workflow-template | workflow.json | Multi-agent pipeline (DAG of steps) |
integration | mcp.json or adapter.json | MCP server, or REST/SOAP adapter contract |
contract | CONTRACT.json | Schema-bearing tool-surface role (ADR-066) that an integration package's [implements.<name>] satisfies |
[package] validation rules
| Field | Rule |
|---|---|
name | [a-z0-9][a-z0-9-]*[a-z0-9], 3–64 chars. No leading/trailing/consecutive hyphens |
version, min_platform_version | Valid semver: MAJOR.MINOR.PATCH, optional -prerelease suffix; each numeric component must parse as an integer |
license | Non-empty; no newlines or tabs (informal SPDX check, not a full identifier list) |
description | Non-empty, ≤500 characters |
dependencies | Key 3–64 chars; value non-empty and must contain a version number; recognized operator prefixes: >= <= > < = ^ ~ * |
standards.openapi_spec | Only valid for skill/integration types |
standards.mcp_compatible | Only valid for skill/integration types |
metadata.keywords | Each ≤50 chars, non-empty; at most 20 entries |
metadata.repository/metadata.homepage | Must start with http:// or https:// if present |
metadata.regions | Each entry must be a currently-assigned ISO 3166-1 alpha-2 code |
Semver constraint syntax
| Syntax | Meaning | Example |
|---|---|---|
* | Any version | Matches everything |
^1.2.3 | Compatible updates | >=1.2.3, <2.0.0 |
~1.2.3 | Patch-level only | >=1.2.3, <1.3.0 |
=1.2.3 | Exact match | Only 1.2.3 |
>=1.0.0, <2.0.0 | Range | Compound constraint |
hoziron-cli package build also auto-derives [dependencies] entries (with
constraint >=0.0.0) for any payload reference (a competency's skills,
an agent template's competencies, a workflow template's
agents_required) not already declared by hand — additive only, never
overwrites an existing hand-set constraint (issue #618).
COMPETENCY.md (payload for type = "competency")
YAML frontmatter delimited by ---, followed by the markdown body — the
body is the system prompt (serde_yaml parses the frontmatter with
deny_unknown_fields, so a typo'd or legacy field fails the build loudly
rather than being silently dropped).
---
description: "Handles first-notice-of-loss intake and triage"
skills: [document-ocr, policy-lookup]
tools: []
contracts: []
integrations: [guidewire-claimcenter]
settings:
- key: strictness
label: "Review Strictness"
description: "How strict the intake validation should be"
type: Select
default: medium
values: [low, medium, high]
schedule: "0 */6 * * *"
schedule_action: "run-triage-sweep"
inputs:
- name: loss_description
type: string
required: true
outputs:
- name: claim_number
type: string
---
You are a claims-intake specialist. Extract the loss description, classify
severity, and route to the correct claim workflow...
Frontmatter fields
| Field | Type | Required | Description |
|---|---|---|---|
description | string | Yes | Non-empty |
skills | string[] | Yes (at least 1) | Kebab-case package names |
tools | string[] | No | |
contracts | string[] | No | Integration contract names this competency invokes |
integrations | string[] | No | |
settings | array of {key, label, description, type, default, values} | No | type must be Select/Text/Toggle/Number; Select requires non-empty values |
inputs / outputs | array of {name, type, required?, values?, range?} | No | type is a free string (string/integer/date/boolean/enum by convention); required only meaningful on inputs; range: [min, max] for integer; values for enum |
schedule | string (cron expression) | No | Requires schedule_action when set |
schedule_action | string | Conditional | Required when schedule is set |
There is no id, name, or category field in COMPETENCY.md
frontmatter — a manifest carrying any of them fails to parse
(deny_unknown_fields). Identity comes from MANIFEST.toml [package] name/title; catalog classification comes from MANIFEST.toml [package.metadata] categories (free-form taxonomy paths, not a fixed
enum). If you're migrating an older competency that had category: Insurance in its frontmatter, delete that line and add the equivalent
path to MANIFEST.toml [package.metadata] categories instead.
Markdown body must be non-empty — it becomes the agent's system prompt.
SKILL.md (payload for type = "skill")
---
version: "1.0.0"
description: "OCR extraction for scanned claim documents"
license: "Apache-2.0"
tools: []
runtime: promptonly
entry: ""
config: {}
---
# Document OCR
Extract structured fields from scanned claim documents...
| Field | Type | Required | Description |
|---|---|---|---|
description | string | Yes | Non-empty |
version | string | No | Valid semver if present |
license | string | No | |
tools | string[] | No | |
runtime | string | No (default promptonly) | Recognized: python, wasm, node, shell, builtin, promptonly, starlark. Only promptonly and starlark are actually permitted to compile (ADR-041) — the others parse but are rejected at build time |
entry | string | Conditional | Required for any runtime other than promptonly/builtin |
config | table | No | Free-form skill runtime config |
promptonly skills require a non-empty markdown body (it becomes
prompt_context).
AGENT.md (payload for type = "agent-template")
---
description: "Front-line FNOL intake agent"
competencies: [claims-intake]
allow_skill_tools: []
allow_integration_tools: []
---
You are the front-line intake agent for new claims...
| Field | Type | Description |
|---|---|---|
description | string | Required |
competencies | string[] | |
allow_skill_tools | string[] | Per-class visibility: in-process skill tools (ADR-052) |
allow_integration_tools | string[] | Per-class visibility: MCP/integration tools (ADR-052) |
There is deliberately no model-selection field — deny_unknown_fields
means a manifest with a model: block fails to parse rather than being
silently honored or dropped. Model selection is routing-gateway/policy
authority (ADR-049/ADR-054), never a package-level lever.
There is also no triggers field — deny_unknown_fields means a manifest
with a triggers: key fails to parse rather than being silently honored
or dropped. Source acceptance is determined structurally instead — see
invocation-model.md.
workflow.json (payload for type = "workflow-template")
Native JSON, not markdown+frontmatter — a workflow is a data definition (DAG of steps), not a document.
{
"description": "FNOL intake through to triage assignment",
"agents_required": ["intake-agent", "triage-agent"],
"steps": [],
"variables": {}
}
| Field | Type | Description |
|---|---|---|
description | string | Required |
agents_required | string[] | Agent template names this workflow references |
steps | array | Step definitions (workflow-engine-specific shape) |
variables | table | Workflow-scoped variables |
mcp.json / adapter.json (payload for type = "integration")
mcp.json matches the de facto MCP ecosystem config shape used by Claude
Desktop, VS Code, Cursor, etc.:
{
"command": "npx",
"args": ["-y", "@company/claimcenter-mcp"],
"env": { "CLAIMCENTER_TOKEN": "" },
"url": null
}
| Field | Type | Description |
|---|---|---|
command | string | Subprocess command (e.g. npx, uvx, node) |
args | string[] | Arguments |
env | table | Env vars for the server process |
url | string | Remote server URL, for streamable-HTTP/SSE transports instead of a subprocess |
If payload/adapter.json is present instead, the package uses a
REST/SOAP adapter contract (ADR-057) — no live MCP handshake; each
operation is dispatched over plain HTTP through the same mediated
conn.call_tool() chokepoint as MCP tools. See the integration authoring
guide for the adapter.json operation schema.
CONTRACT.json (payload for type = "contract")
Promotes a "contract" (ADR-051's named tool-surface role on a competency)
from a name-only string to a standalone, versioned artifact carrying a
real JSON Schema per tool (ADR-066, Issue #792). A contract package
carries no executable behavior — the payload is pure schema data, authored
once as the product-level definition of an interface, checked against by
every integration that implements it (see [implements.<name>] above).
{
"schema_version": "1",
"tools": [
{
"name": "create_claim",
"description": "File a new claim against a policy.",
"input_schema": {
"type": "object",
"properties": {
"policy_ref": { "type": "string" },
"loss_date": { "type": "string", "format": "date" }
},
"required": ["policy_ref", "loss_date"]
},
"output_schema": { "type": "object", "properties": { "claim_id": { "type": "string" } } }
}
]
}
| Field | Type | Description |
|---|---|---|
schema_version | string | Forward-compat version for this JSON shape itself (own field, not coupled to min_platform_version); defaults to "1" |
tools | array | The tool surface this contract declares. Order is preserved for display but not semantically significant — lookups are by name |
tools[].name | string | Canonical tool name (e.g. create_claim). This is the vocabulary the agent/model always sees — an implementing integration's native name, if different, is reconciled via its tool_map |
tools[].description | string, optional | Human-readable description |
tools[].input_schema | JSON Schema | Full JSON Schema for the tool's input — the exact shape an MCP tools/list response carries in Tool.input_schema |
tools[].output_schema | JSON Schema, optional | Expected return shape. Not enforced by the connect-time conformance check (only inputs are structurally diffable against a live MCP tools/list) — carried for documentation and future use |
COLLECTION.toml
name = "insurance-starter"
display_name = "Insurance Starter Kit"
description = "Everything needed to start processing claims"
version = "1.0.0"
license = "MIT"
min_platform_version = "0.5.0" # optional
featured = true
tags = ["insurance", "claims", "starter"]
icon = "shield-check"
[author]
name = "Hoziron Team"
email = "platform@hoziron.com" # optional
url = "https://hoziron.com" # optional
[metadata]
categories = ["insurance"]
regions = ["ZA"]
[[packages]]
name = "claims-intake"
version = "1.2.0"
package_type = "competency"
is_dependency = false
Unlike MANIFEST.toml, COLLECTION.toml does carry a free-text
[author] (name required, email/url optional) — collections are an
admin-curated resource type where self-declared attribution is acceptable.
Closure invariant
A collection must include ALL transitive dependencies. Installing a collection never requires fetching packages from outside it.
Related: