| name | shape-system |
| description | The canonical recipe for working with the Shape System — the platform-wide structured-content registry (content_ir.kind_definition + kind_surface / kind_component / kind_example). Use whenever a task involves adding or activating a kind ("add a kind", "new shape", "new structured content type", "render block" for a kind-shaped payload), writing or reading `__kind` JSON, kind schemas, samples ("sample", "dual gate", "activate a kind"), detection surfaces (XML tag / fence language → kind), kind components, content blocks or skills that teach a kind, workflow node input_kind/output_kind, or binding an agent's structured output to a kind. Read BEFORE touching any content_ir table or any kind asset. Never build a parallel registry, detector, or sample store. |
Shape System — how to add to it (and never around it)
Glossary: "Shape" = product name; "kind" = the technical noun (content_ir.kind_definition.kind). One kind = one named, versioned structure that every platform (web, Python, workflow-studio, React Native later) validates and renders against.
Operating doc (read it): features/content-ir/docs/SHAPE_SYSTEM.md — rulings, posture, roadmap. FEATURE.md invariants: features/content-ir/FEATURE.md.
The model in three lines
- A Shape carries 7 assets: schema · skill(s) · content block · output component · input component · samples (
kind_example) · pydantic mirror.
- Every input surface (
__kind JSON, XML tag, fence, tool result) converges to canonical __kind JSON at DETECTION (kind_surface is the one list). Below that boundary everything is format-agnostic by construction.
- Consumers: agent outputs, workflow node I/O (live), tool results (Stage 7), custom apps.
Adding a kind (the paved road)
- Schema — insert the
kind_definition row (fields in data[] for ts-owned; emitted_json_schema always; org = system org 39c38960-d30c-4840-b0c1-c9960de95582 for platform kinds, visibility='public'). Python-owned kinds derive the schema from the pydantic model: Model.model_json_schema() — never hand-write what a model can emit. Idempotent SQL, applied via Supabase MCP, ledgered in public._schema_migrations.
- Sample — one
kind_example row per kind@version (is_canonical=true). Never write validation_status yourself — the kind_example_recompute_validation trigger DERIVES it on every write, so a fabricated passed is both impossible and a defect. Helper: pnpm shape:sample <kind> --file|--stdin [--apply].
- Component — register the web component. Two tiers, both satisfy the render leg: compiled (
kinds/<slug>.ts → append to SYSTEM_KIND_DEFINITIONS → register the block type in block-dispatch.tsx's SHAPE_BLOCK_DISPATCH; never add a switch case) or DB-authored (a source='db' kind_component row — no repo change, no deploy; what the creator agent writes). A bundled row's component_key MUST resolve in block-dispatch.tsx — pnpm check:shapes:components is a blocking release gate, because a dangling key changes nothing at runtime (the compiled bridge routes anyway) while the registry claims coverage. A kind without either stays inactive and renders the generic viewer (correct, not a failure).
- Skill + content block — one skill per kind per syntax (
kind_<slug> JSON / kind_<slug>_xml); skill bodies teach the REAL parser failure modes. Blocks pair with the skill under the Agent Skills category (two per skill: simple + complex), linked via platform.associations. Coexist-not-clobber: never overwrite a live legacy block; use a -kind suffix during transition.
- Surface — ONLY for a non-JSON arrival form (XML tag / custom fence language).
__kind JSON needs NO surface: the parser detects it natively in a ```json fence. A row is inert without its implemented in , so registering one without the parser mints a phantom row (8 such rows already exist — do not add a ninth). After inserting, run — the compiled bootstraps in BOTH repos are generated from . Never hand-add a tag/fence literal to / / .
Workflow node I/O (live)
NodeSpec.output_kind is MANDATORY for non-dynamic nodes (structural kind for stable shapes; generic json minimum). input_kind gates inputs pre-execute. Per-node authored overrides: data.input_kind / data.output_kind. Read packages/matrx-graph/docs/node-authoring.md §kinds (aidream repo) before adding a node.
- Runtime:
matrx_graph.kinds.get_kind / validate_against_kind (loud-fail-open). After ANY kind registry mutation call invalidate_kind_catalog_cache() (and remember the FE warm registry re-reads on its own cadence).
- LLM binding:
matrx_ai.kinds.response_format_for_kind(slug) → strict portable response_format. Unknown/unportable kinds decline loudly with None.
Hard rules
- One registry. Never a parallel kind list, detector, sample store, or component map. Extending
content_ir IS the feature.
- Verify live, never trust reports — after any DB write,
execute_sql the counts; after any render change, a runtime marker or screenshot.
- XML is not legacy — and neither is anything else named "legacy" in this feature. Every
*-legacy-text.ts, every *_legacy_text strategy key, legacyBlockType, and toLegacyServerData is LIVE code carrying migration-narrative vocabulary that froze into identifiers. legacyBlockType in particular is THE render key — kind-route.ts sets block.type from it and block-dispatch.tsx routes on it; nothing renders without it. Never read the name as permission to delete or "modernize". Rename pending (~100 files + 23 DB rows + an aidream generated file).
- Render-leg satisfier order is load-bearing. The compiled-bridge check runs FIRST because it actually exercises the bridge; the resolved
kind_component row is the last resort. Reversing them short-circuits the "No <kind> available" guard for every compiled kind. Pinned by tests — do not reorder.
- Matrx Directives (MatrxEnvelope) is off-limits except the collision guard and its
kind_surface row. Its invariants are listed in SHAPE_SYSTEM.md.
sample_data on kind_definition is interim — new samples go to kind_example; never add new readers of sample_data.
- DB changes follow the db-change skill (MCP apply → ledger →
pnpm db-types → aidream python db/generate.py → live verify → both repos commit).
Definition of done
The doctor SHIPPED — pnpm check:shapes generates features/content-ir/docs/SHAPES_STATUS.md (8 asset columns per kind) and scripts/shape/shapes-status.json. Your kind's row is green and a preview screenshot exists.
Read the report's n/a doctrine before "closing gaps": a nested_only_child (renders only inside its parent) and a data_only contract kind are STRUCTURALLY exempt from component/skill/block/surface. Those cells are derived, never declared — building assets for them is wasted work, and the doctor will keep marking them n/a.
A fabricated validation_status='passed' is a defect; so is an is_active flipped by anything other than set_kind_activation.