How to Emit a DCP Message
Emitting a DCP message means constructing a DcpMessage envelope carrying exactly one Event, then handing that envelope to any transport for delivery. DCP itself never sends anything — it only defines what a valid message looks like. This guide walks a producer through building one, from choosing a verb to passing the finished message off.
1. Choose an entity type and verb
Section titled “1. Choose an entity type and verb”Every event describes a change to one of the eight canonical nouns — Task, Decision, Finding, and so on. Pick the entity type that matches what changed, then pick a verb that describes what happened to it: task.completed, decision.recorded, finding.raised. Verbs are open tokens with a controlled vocabulary — consumers must tolerate verbs they don’t recognize — but entity_type itself is a closed set in v1, so stick to the eight.
2. Build the Event
Section titled “2. Build the Event”The Event is the body of the message. It requires event_id, entity_type, verb, and entity_id, and it must contain at least one of entity, delta, or refs — an event with no payload is invalid.
A delta reports a change set, { field: { from?, to } }, where to is required and from is optional. Here is a real one, from a task.completed event:
"delta": { "status": { "from": "in_progress", "to": "completed" }}refs links the event to other entities using the closed rel vocabulary (relates_to, part_of, references, caused_by, derived_from, supersedes, concerns, blocks). Here is a real one, from a finding.raised event that concerns an earlier decision:
"refs": [ { "rel": "concerns", "ref": "decision_thin_envelope" }]You may also attach attributed_to, occurred_at, and project_id — all optional, all untrusted hints rather than protocol guarantees. Full field definitions live in the Event reference.
3. Wrap it in the envelope
Section titled “3. Wrap it in the envelope”The Event becomes the body of a DcpMessage. The envelope adds four required fields:
dcp_version—"1.0"for this revision.message_id— an opaque, producer-asserted identifier prefixedmsg_.message_type—<entity_type>.<verb>in dot notation.issued_at— an ISO-8601 UTC timestamp with a trailingZ, marking when the message was composed, not delivered.
See the envelope reference for the complete field list, including the optional correlation_id and extensions.
4. Keep message_type in sync with the body
Section titled “4. Keep message_type in sync with the body”message_type is a denormalized convenience copy of <body.entity_type>.<body.verb> — useful for a human or operator glancing at a log without parsing the body. It is not authoritative. A conformant validator must check that message_type equals entity_type.verb, and on any conflict the body wins and the message is malformed. This is the one rule plain JSON Schema cannot express on its own — a cross-field equality check — so any implementation you write or consume needs it as an explicit addition to schema validation. Check your own output against this rule before shipping; see how to validate a DCP message for the tooling.
5. Hand it to a transport
Section titled “5. Hand it to a transport”DCP is transport-neutral: it does not deliver, authenticate, route, or acknowledge anything. Once your DcpMessage validates, hand the raw JSON to whatever transport your system already uses — a message queue, a webhook, AgentixMesh, a file drop. That transport layer owns delivery guarantees, retries, and identity; DCP only guarantees that the payload it carries is well-formed and self-describing.
A complete example
Section titled “A complete example”This is a real, valid message from the repository’s worked examples — a finding.raised event referencing a prior decision:
{ "dcp_version": "1.0", "message_id": "msg_01HZ0FINDINGRAIS1", "message_type": "finding.raised", "issued_at": "2026-06-29T10:20:00Z", "correlation_id": "msg_01HZ0REVIEWREQ001", "body": { "event_id": "evt_01HZ0FINDINGRAIS1", "entity_type": "finding", "verb": "raised", "entity_id": "finding_actor_rename", "attributed_to": "security-reviewer", "entity": { "id": "finding_actor_rename", "subject_ref": "review_envelope_design", "severity": "medium", "category": "security", "title": "Rename 'actor' to 'attributed_to' to avoid identity confusion", "description": "The term 'actor' reads as an authenticated principal; a name that signals unverified attribution reduces trust-confusion.", "location": { "ref": "schemas/v1/event.schema.json" }, "status": "resolved" }, "refs": [ { "rel": "concerns", "ref": "decision_thin_envelope" } ] }}Notice attributed_to: "security-reviewer" — a descriptive, untrusted label, not an authenticated identity. Anyone can write any string there; DCP asserts nothing about who actually emitted the message. See Provenance, Not Identity for why this distinction matters and what a consumer may — and may not — infer from it.
Both message_id and entity_id are opaque, producer-asserted strings. DCP does not guarantee they are globally unique or that they resolve to anything; scoping and deduplication are the consumer’s or transport’s job, not the protocol’s. If you’re new to DCP entirely, the quickstart walks through validating your first message end to end.
By InterIP Networks · Last updated 2026-07-01.