Build
Get started
Read the short documents, run the conformance corpus, validate your own message with format assertion on, and publish it. Then pick a tier and talk to the working group.
Step 1
Read four things, in this order
Introduction ↗
Why SOM exists, the three nouns, and the six principles.
02 · for implementersConformance ↗
What it means to be SOM 1.0 conformant — shorter than you expect.
03 · the clearest pictureThe hurricane run ↗
One story told across seven snapshots.
04 · your first familystory-context schema ↗
The family you will almost certainly implement first.
Step 2
Run the conformance corpus
The specification repository ships worked examples that must validate, twenty negative cases that must be rejected, and pure-Python tools that check both.
git clone https://github.com/storyobjectmodel/som.git
cd som
pip install jsonschema rfc3339-validator
python3 tools/validate.py # every example is legal
python3 tools/validate_negative.py # every negative case is rejected
python3 tools/som_lint.py schema # schemas agree with their own claims
python3 tools/validate_sequence.py examples/hurricane-run
# a story over time holds together- Is this message legal? —
validate.py - Do the schemas actually constrain? —
validate_negative.py. Nineteen cases fail on the schemas; the twentieth,som_version: "0.3.2", fails on the wire-version rule, which your implementation must apply itself. - Does the schema match what it says about itself? —
som_lint.py - Does a run of snapshots hold together? —
validate_sequence.py, where the expensive bugs live.
Install rfc3339-validator. Without it, Python’s jsonschema quietly accepts a malformed timestamp. The negative corpus includes that case precisely so the check fails loudly when the package is missing.
Step 3
Validate in your own stack — with formats asserted
Two implementations that disagree about whether message_id must be a UUID are not interoperable. In Node that means Ajv’s 2020-12 build plus ajv-formats.
// npm i ajv ajv-formats
import Ajv2020 from 'ajv/dist/2020';
import addFormats from 'ajv-formats';
const BASE = 'https://storyobjectmodel.com/schema/1.0';
const FAMILY_SCHEMA: Record<string, string> = {
'story.context': 'story-context',
'skill.warning.raised': 'skill-warning',
'delivery.media_available': 'delivery-media-available',
'link.committed': 'link-event', 'link.gate_changed': 'link-event', 'link.withdrawn': 'link-event',
'telling.started': 'telling-event', 'telling.ended': 'telling-event', 'telling.exposed': 'telling-event',
'system.audit': 'system-audit',
};
const ajv = new Ajv2020({ strict: false, allErrors: true });
addFormats(ajv); // conformance §4: uuid and date-time MUST be asserted
const load = async (name: string) => (await fetch(`${BASE}/${name}.schema.json`)).json();
export async function validateMessage(msg: { som_version?: string; message_type?: string; payload?: unknown }) {
const envelope = ajv.compile(await load('envelope'));
if (!envelope(msg)) return { ok: false, errors: envelope.errors };
// §3: 0.3.2 is not SOM 1.0 — and never branch on the version otherwise.
if (msg.som_version?.startsWith('0.')) return { ok: false, errors: ['pre-1.0 som_version'] };
// §2 and §5: dispatch on message_type only; ignore families you don't handle.
const family = FAMILY_SCHEMA[msg.message_type ?? ''];
if (!family) return { ok: true, ignored: true };
const payload = ajv.compile(await load(family));
return payload(msg.payload) ? { ok: true } : { ok: false, errors: payload.errors };
}Illustrative, not a reference implementation — cache compiled validators rather than fetching per message. The schemas are served at the URL in their own $id, so resolving by identifier works too.
Step 4
Publish a conformant story snapshot
{
"som_version": "1.0.0",
"message_id": "0199a1c4-7a2e-7b31-8c55-4d2f9e6a1b07",
"correlation_id": "0199a1c4-0000-7000-8000-000000000911",
"message_type": "story.context",
"timestamp": "2026-09-11T16:41:00.000000Z",
"originating_system": {
"system_id": "ncs-nyc-01", "system_type": "ncs"
},
"topic": "som.story.context.hurricane-2026-0911",
"payload": {
"story_id": "hurricane-2026-0911",
"slug": "HURRICANE-LANDFALL",
"headline": "Hurricane makes Cat 4 landfall",
"story_type": "ACTIVE",
"lifecycle": { "phase": "BREAKING",
"phase_entered_at": "2026-09-11T14:00:00Z" },
"sequence_number": 7,
"updated_at": "2026-09-11T16:41:00Z"
}
}- A fresh UUID
message_id(v7 recommended); the samecorrelation_idfor the story’s lifecycle. - The same
story_id, a highersequence_number, a laterupdated_at. - Every field, including the ones other systems contributed. Omission means absent.
lifecyclepresent if and only ifstory_typeisACTIVE.- Your own
originating_system, re-stamped on every publish. - Anything the standard doesn’t carry goes under
extensions["com.{vendor}.…"].
See it on a live bus. The project’s emulator ↗ is a dashboard on a running bus you can use yourself, and the IBC demos ↗ show two full scenarios end to end. A .NET reference implementation is to follow in a separate repository; the pre-1.0 hackathon starter emits 0.3.2 and is not 1.0 traffic.
Joining
Nothing about your product has to come out
One system is enough to start. Integrations are described in three tiers.
Subscribe and publish
Read the story and put your own family on the bus. The project estimates this in days, not months.
Bidirectional context
Your tool both consumes and contributes structured context — assertions, provenance, links, tellings.
Decide against the story
Your tool acts autonomously on what it reads, through an executor applying the house’s skills.
Get involved
The working group is open
Register interest ↗
Build against it, write skills, run a Story Archaeology session, join the working group, or just follow the work — the official form asks who you are and what you want to do.
storyobjectmodel.com form →Propose a change ↗
Every change arrives as a pull request, and anyone may open one. Schema changes are bound by the compatibility policy; open questions live in the open register.
GOVERNANCE · CONTRIBUTING →Talk an integration through
For anything the repository README doesn’t answer, write to the project.
hello@storyobjectmodel.com →The schemas win. When any prose — this site included — disagrees with a published schema, the schema is right. Browse the seven families →