{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://github.com/bobmatnyc/trusty-tools/schemas/instruction-package-v2.json",
"title": "trusty-mpm instruction package",
"description": "A PM instruction package: a section taxonomy carrying the customization-tier axis, plus an ORDERED block stream that composes to the PM system prompt. Ordering is array order — never map iteration order — so composition is byte-deterministic (SPEC-PMINSTR-01 / DOC-59 §4a, epic #4183, schema ticket #4184).",
"type": "object",
"additionalProperties": false,
"required": [
"schema_version",
"package_id",
"sections",
"blocks"
],
"properties": {
"schema_version": {
"description": "Schema major version. A package whose version this build does not implement is rejected outright — never partially interpreted; the version is checked BEFORE any field, so a newer package reports a version error rather than blaming one of its own valid keys. EVOLUTION POLICY: every object here sets `additionalProperties: false`, so unknown keys are errors, not ignored. Any field addition that can change composed bytes MUST bump this version. Adding a field while leaving the version unchanged is the one case strictness cannot protect against — an older build would compose a prompt missing the new field's effect and report success. HISTORY: v1 (#4184) shipped `text` and `generated` bodies; v2 (#4318) adds the `file` body kind so an authored JSON manifest can reference bundled markdown instead of inlining 23 KB of prose on one line.",
"const": 2
},
"package_id": {
"description": "Stable identifier for this package (e.g. `trusty-mpm/pm`). Identity only; it never affects composed bytes.",
"type": "string",
"minLength": 1
},
"description": {
"type": "string"
},
"trailing_newline": {
"description": "Append exactly one '\\n' after the last emitted block. `false` reproduces today's `resolve_pm_prompt` output; `true` reproduces a file-shaped artifact.",
"type": "boolean",
"default": false
},
"sections": {
"description": "The section taxonomy. Declaration order MUST be the canonical order (identity, core, memory, search, workflow, agent-delegation, enforcement, non-overridable-rules, framework-guaranteed-conventions) and every canonical section MUST be declared exactly once. Declaration order does not affect composed bytes — `blocks` alone determines output order.",
"type": "array",
"items": {
"$ref": "#/$defs/section"
},
"minItems": 9,
"maxItems": 9
},
"blocks": {
"description": "The ordered composition stream. Emitted strictly in array order. A section may own several non-contiguous blocks, which is what lets a section be lifted out of the middle of a legacy asset without moving a single byte.",
"type": "array",
"items": {
"$ref": "#/$defs/block"
},
"minItems": 1
}
},
"$defs": {
"section-id": {
"description": "The closed section taxonomy, in canonical declaration order. `core` is the only section a named-section override cannot replace (tier `fixed`); every other section is tier `project` and is replaceable (#4286).",
"type": "string",
"enum": [
"identity",
"core",
"memory",
"search",
"workflow",
"agent-delegation",
"enforcement",
"non-overridable-rules",
"framework-guaranteed-conventions"
]
},
"customization-tier": {
"description": "Who may override this section, by increasing permissiveness. `fixed`: nobody — reserved for `core`, the single protected section (#4286). `project`: a project-tier override only — deliberately NOT a user-tier one, so a personal machine config can never silently leak into a shared project. `user`: both project- and user-tier, project winning on collision (most-specific-wins).",
"type": "string",
"enum": [
"fixed",
"project",
"user"
]
},
"generator": {
"description": "A named composition-time input the host must supply. Generated content is never authored in the package — it is computed per project/session and passed in.",
"type": "string",
"enum": [
"agent-roster",
"stack-profile",
"project-addendum"
]
},
"join": {
"description": "The literal bytes inserted BEFORE this block when it is not the first emitted block. `rule` = \"\\n\\n---\\n\\n\", `blank` = \"\\n\\n\", `none` = \"\", `{\"literal\": \"...\"}` = verbatim. Explicit joins are what make a faithful, byte-identical lift possible: blocks split out of one legacy document rejoin with `blank`, while distinct legacy assets rejoin with `rule`.",
"oneOf": [
{
"type": "string",
"enum": [
"rule",
"blank",
"none"
]
},
{
"type": "object",
"additionalProperties": false,
"required": [
"literal"
],
"properties": {
"literal": {
"type": "string"
}
}
}
]
},
"body": {
"description": "Where a block's markdown comes from. `text` is authored inline in the package; `file` names a markdown source bundled with the build; `generated` is supplied at composition time by the named generator.",
"oneOf": [
{
"type": "object",
"additionalProperties": false,
"required": [
"kind",
"text"
],
"properties": {
"kind": {
"const": "text"
},
"text": {
"type": "string",
"minLength": 1
}
}
},
{
"type": "object",
"additionalProperties": false,
"required": [
"kind",
"path"
],
"description": "Schema v2 (#4318). The path is resolved against the build's compile-time `include_str!` table (`instruction_pipeline::SECTION_SOURCES`), NOT against the filesystem at composition time — so the delivered prompt never depends on what happens to be on disk, and a renamed section is a compile error rather than a silent content drop. A path the build does not bundle is a hard validation error. Prose therefore keeps living in reviewable markdown while this document carries only the manifest; `text` remains available for a rule authored in the manifest itself.",
"properties": {
"kind": {
"const": "file"
},
"path": {
"description": "Path relative to `assets/instructions/`, e.g. `sections/core.md`.",
"type": "string",
"minLength": 1
}
}
},
{
"type": "object",
"additionalProperties": false,
"required": [
"kind",
"generator"
],
"properties": {
"kind": {
"const": "generated"
},
"generator": {
"$ref": "#/$defs/generator"
}
}
}
]
},
"section": {
"type": "object",
"additionalProperties": false,
"required": [
"id",
"title",
"customization_tier"
],
"properties": {
"id": {
"$ref": "#/$defs/section-id"
},
"title": {
"type": "string",
"minLength": 1
},
"customization_tier": {
"$ref": "#/$defs/customization-tier"
},
"description": {
"type": "string"
}
}
},
"block": {
"type": "object",
"additionalProperties": false,
"required": [
"section",
"body"
],
"properties": {
"section": {
"$ref": "#/$defs/section-id"
},
"body": {
"$ref": "#/$defs/body"
},
"join_before": {
"$ref": "#/$defs/join",
"default": "rule"
},
"optional": {
"description": "Only a `generated` block may be optional. An optional block is dropped when its generator supplies no content; a non-optional block whose content is missing or empty is a hard composition error, never a silent drop (issue #4196 — the computed 42-agent roster was discarded at the final composition step and the prompt shipped naming 8).",
"type": "boolean",
"default": false
}
}
}
},
"examples": [
{
"schema_version": 2,
"package_id": "trusty-mpm/example",
"description": "Minimal structurally-valid package, exercising all three body kinds. The shipped instance is `pm-instruction-package.json`; this example exists so the schema's own shape stays executable and testable.",
"trailing_newline": false,
"sections": [
{
"id": "identity",
"title": "Identity",
"customization_tier": "project",
"description": "Absorbed BASE_PM `## Identity`."
},
{
"id": "core",
"title": "Core PM Instructions",
"customization_tier": "fixed"
},
{
"id": "memory",
"title": "Memory",
"customization_tier": "project"
},
{
"id": "search",
"title": "Search",
"customization_tier": "project"
},
{
"id": "workflow",
"title": "Workflow",
"customization_tier": "project"
},
{
"id": "agent-delegation",
"title": "Agent Delegation",
"customization_tier": "project",
"description": "Dynamic: built from the deployed-agent roster at composition time."
},
{
"id": "enforcement",
"title": "Delegation Enforcement",
"customization_tier": "project",
"description": "Prohibitions + Circuit Breakers (#4573)."
},
{
"id": "non-overridable-rules",
"title": "Non-Overridable Rules",
"customization_tier": "project",
"description": "Absorbed BASE_PM `## Non-Overridable Rules`, `## Customizing PM Behavior`, `## Trusty Tool Priority (Non-Overridable)`."
},
{
"id": "framework-guaranteed-conventions",
"title": "Framework-Guaranteed Conventions",
"customization_tier": "project",
"description": "Absorbed BASE_PM `## Framework-Guaranteed Conventions (Non-Overridable)`."
}
],
"blocks": [
{
"section": "core",
"body": {
"kind": "text",
"text": "# PM Agent -- Trusty MPM"
}
},
{
"section": "memory",
"join_before": "blank",
"body": {
"kind": "text",
"text": "## Context-First Protocol"
}
},
{
"section": "core",
"join_before": "blank",
"body": {
"kind": "text",
"text": "## Agent Routing"
}
},
{
"section": "search",
"body": {
"kind": "text",
"text": "## Code/Architecture Search"
}
},
{
"section": "core",
"body": {
"kind": "generated",
"generator": "stack-profile"
},
"optional": true
},
{
"section": "workflow",
"body": {
"kind": "text",
"text": "# PM Workflow Configuration"
}
},
{
"section": "agent-delegation",
"body": {
"kind": "generated",
"generator": "agent-roster"
}
},
{
"section": "core",
"body": {
"kind": "generated",
"generator": "project-addendum"
},
"optional": true
},
{
"section": "identity",
"body": {
"kind": "file",
"path": "sections/identity.md"
}
},
{
"section": "enforcement",
"join_before": "blank",
"body": {
"kind": "text",
"text": "## Prohibitions"
}
},
{
"section": "non-overridable-rules",
"join_before": "blank",
"body": {
"kind": "text",
"text": "## Non-Overridable Rules"
}
},
{
"section": "framework-guaranteed-conventions",
"join_before": "blank",
"body": {
"kind": "text",
"text": "## Framework-Guaranteed Conventions"
}
}
]
}
]
}