§3 Manifest
Normative schema for surface.json (identity, adapter, Binding, commands, triggers, protocol metadata) and the validation a runtime performs.
The manifest is surface.json at the pack root. It MUST be a JSON object.
3.1 Top-level fields#
| Field | Type | Req | Constraint |
|---|---|---|---|
id | string | MUST | pack_id, 1 to 128 chars, lower-case |
name | string | MAY | Display name |
cliBin | string | SHOULD | The CLI binary the Skill teaches |
kind | enum | SHOULD | wrap | composite | openapi | sense_only. Alias adapter_kind. Default composite |
plane | enum | MAY | runtime | control. Default runtime |
transports | array | MAY | Subset of cli, json_exec, mcp; at most 4 entries |
binding | object | SHOULD | §3.3 |
wrap | object | conditional | MUST when kind is wrap |
openapi | object | conditional | MUST when kind is openapi |
pass_through | object | MAY | §3.5; wrap only |
verbs | object | MUST | Map verb_key → command (§3.4). MAY be empty only when kind is sense_only |
stimulusTriggers | array | MAY | { ref: string }[] |
skill_url | string | MAY | URL to a hosted copy of the Skill |
harness | string | MAY | Inline Skill text (deprecated in favour of HARNESS.md) |
Runtimes MUST ignore unknown top-level fields in surface.json for forward
compatibility, and SHOULD report them.
3.2 Adapter fields#
wrap#
| Field | Type | Req | Constraint |
|---|---|---|---|
bin | string | MUST | 1 to 128 chars. Relative paths (.-prefixed) resolve against the pack root |
argv_template | string[] | MAY | ≤ 64 entries. {{verb}} and {{name}} placeholders. Default ["{{verb}}"] |
agent_env | object | MAY | String → string. Merged into the child environment |
openapi#
| Field | Type | Constraint |
|---|---|---|
url | string (URL) | One of url or path MUST be present |
path | string | Relative to the pack root |
3.3 Binding fields#
| Field | Type | Constraint |
|---|---|---|
mode | enum | user | mothership | local | public | mock |
hub | enum | keyring | local-credential | public | none |
family | string | 1 to 128 chars |
connect_provider_id | string | 1 to 128 chars |
Unknown Binding fields MUST fail validation.
3.4 Command entry#
Each value of verbs is an object.
| Field | Type | Req | Constraint |
|---|---|---|---|
description | string | SHOULD | One sentence |
cli | string[] | SHOULD for wrap | First token is the bin. Placeholders {{name}} and --flag={{name}} |
args | object | MAY | JSON Schema for the command's arguments |
policy | enum | MAY | read | write. Informative hint; policy.json is authoritative |
3.5 Pass-through (HCP/1.1)#
| Field | Type | Req |
|---|---|---|
allowedSubcommands | string[] | MUST, ≥ 1 |
maxArgv | integer | MUST, > 0 |
- A
wrappack MUST declare either a non-emptyverbsmap orpass_through. - A pack relying on
pass_throughMAY be installed as a bootstrap but is not a conforming HCP/1.1 pack until its hot path is promoted to named commands.
3.6 Protocol metadata#
A manifest MAY carry, or a runtime MUST derive, the following object. It is validated strictly: unknown fields MUST fail.
type HarnessPackMeta = {
hcp: 1
pack_id: string // = id
adapter_kind: 'composite' | 'wrap' | 'openapi' | 'sense_only'
binding?: HarnessBinding
wrap?: HarnessWrapTarget
openapi?: HarnessOpenApiRef
pass_through?: HarnessPassThrough
plane?: 'runtime' | 'control'
transports?: ('cli' | 'json_exec' | 'mcp')[]
skill_url?: string
}3.7 Validation#
A runtime MUST reject the manifest when any of the following holds.
| Condition | Issue |
|---|---|
id missing or ≠ Policy surface_id | invalid_meta / missing_verb (id) |
kind: wrap without wrap.bin | binding: wrap adapter requires wrap.bin |
kind: wrap with empty verbs and no pass_through | wrap: wrap requires named commands or pass_through |
plane: control with binding.mode ≠ mothership | binding: control plane packs default Binding mothership |
kind: openapi without openapi | invalid_meta: openapi adapter requires openapi ref |
kind: sense_only with no stimulusTriggers | invalid_meta: sense_only adapter requires at least one stimulus trigger_ref |
| Metadata schema violation | invalid_meta |
3.8 Example (informative)#
{
"id": "ledger",
"name": "Ledger",
"kind": "wrap",
"cliBin": "ledger",
"plane": "runtime",
"transports": ["cli", "json_exec"],
"binding": { "mode": "local", "hub": "local-credential", "family": "ledger" },
"wrap": { "bin": "../../bins/ledger.mjs", "argv_template": ["{{verb}}"] },
"verbs": {
"setup.status": { "description": "Idempotent installer status.", "cli": ["ledger", "setup", "status"] },
"setup.apply": { "description": "Apply installer.", "cli": ["ledger", "setup", "all", "-y"], "policy": "write" },
"connect": { "description": "Connect Binding.", "cli": ["ledger", "connect", "{{provider}}"], "policy": "write" },
"status": { "description": "Connect health.", "cli": ["ledger", "status"] },
"list": { "description": "Domain read.", "cli": ["ledger", "list"] },
"transfer": { "description": "Gated write.", "cli": ["ledger", "transfer", "{{amount}}", "--to", "{{payee}}", "--confirm"], "policy": "write" }
}
}