What
hadron chat post currently writes the message body into the node's data block (data.body), alongside the structured fields. The body should live in Node.content — it's prose, and that's what content is for. data keeps the structured metadata (author, identity, role, timestamp, mentions).
Why
- A message body is the node's content; splitting prose into a JSON sub-field is surprising to anyone reading the how-to (it's the first thing people trip over).
content renders as the node body in the portal, so messages become directly readable instead of a JSON blob.
content is an embedding source, so chat becomes semantically searchable — "what did we decide about the exit page?" is a genuinely useful query over a team chat.
- It's consistent with how every other prose-bearing node works.
Changes (CLI)
internal/cmd/chat/post.go:79 — build the node with content: <body>; drop body from the data map, keep author/identity/role/timestamp/mentions.
internal/cmd/chat/chat.go parseMessage (L126) — currently takes only data. Take content as well and prefer it, falling back to data.body when content is empty.
internal/cmd/chat/read.go:112 — passes parseMessage(n.Loc, n.Seq, n.Data); thread n.Content through, and verify the findNodes projection actually requests content (the chat read path doesn't fetch it today).
Back-compat — please keep a read fallback
Existing chats already hold bodies in data.body (the academy chat alone has 50+ messages). chat read should read content and fall back to data.body, so old history stays readable. I'd keep that fallback permanently rather than only during a migration: chat post isn't the only producer — the hadron-client push channel writes messages, and agents were previously instructed by the docs to hand-roll node create --type message --data-file, so a mixed corpus will exist for a while. A one-off backfill of existing messages is optional on top.
Other writers to keep in sync, or the corpus splits: the hadron-client push channel, and any server-side surface that composes chat messages.
Consequences worth handling deliberately
These are all things data was implicitly exempt from (verified in hadron-server), so they become live once the body moves to content:
- Embedding cost/benefit. Only
content and abstract are embedding sources (EmbedPlaintext { content?, abstract? } in src/jobs/embedNodes.ts; Memory.embeddingSource = abstract | contentChunks | both) — data never is. On a vector-indexed memory, every chat turn will now be chunked + embedded. That's the semantic-search upside above, but it's per-turn API cost and vector rows, so it's worth a conscious choice of embeddingSource for chat memories.
- Abstract staleness.
abstractOriginHash is compared against computeContentHash(node.content). Messages have no abstract, so this should be benign — worth confirming no stale-abstract signal starts showing on message nodes.
- Revision snapshots. Node updates snapshot prior state into
NodeRevision. Messages are append-only, so this should be negligible.
- Encrypted memories.
content goes through the encryption path; confirm chat read renders correctly (and that the fallback doesn't leak ciphertext into the transcript) on an encrypted chat memory.
Docs to update alongside
hadron-docs/docs/how-to/agent-team-chat.md — "The data model" section shows the body inside data.
hadron-docs/docs/reference/node-types.md — mentions the record/message distinction for high-volume content.
- Any team prompt/protocol nodes documenting the message shape (e.g.
hadronmemory.com::experiments::team-chat:academy:prompt-template) — these describe the data payload for hand-rolled messages.
Note
While here: hadron node ls --seq-gt/--sort-seq silently hides the newest messages past the default 50-node page (#319) — the docs' "raw equivalent" snippet for chat read recommends exactly that pattern, so it's worth correcting in the same docs pass.
What
hadron chat postcurrently writes the message body into the node'sdatablock (data.body), alongside the structured fields. The body should live inNode.content— it's prose, and that's whatcontentis for.datakeeps the structured metadata (author,identity,role,timestamp,mentions).Why
contentrenders as the node body in the portal, so messages become directly readable instead of a JSON blob.contentis an embedding source, so chat becomes semantically searchable — "what did we decide about the exit page?" is a genuinely useful query over a team chat.Changes (CLI)
internal/cmd/chat/post.go:79— build the node withcontent: <body>; dropbodyfrom thedatamap, keepauthor/identity/role/timestamp/mentions.internal/cmd/chat/chat.goparseMessage(L126) — currently takes onlydata. Takecontentas well and prefer it, falling back todata.bodywhen content is empty.internal/cmd/chat/read.go:112— passesparseMessage(n.Loc, n.Seq, n.Data); threadn.Contentthrough, and verify thefindNodesprojection actually requestscontent(the chat read path doesn't fetch it today).Back-compat — please keep a read fallback
Existing chats already hold bodies in
data.body(the academy chat alone has 50+ messages).chat readshould readcontentand fall back todata.body, so old history stays readable. I'd keep that fallback permanently rather than only during a migration:chat postisn't the only producer — the hadron-client push channel writes messages, and agents were previously instructed by the docs to hand-rollnode create --type message --data-file, so a mixed corpus will exist for a while. A one-off backfill of existing messages is optional on top.Other writers to keep in sync, or the corpus splits: the hadron-client push channel, and any server-side surface that composes chat messages.
Consequences worth handling deliberately
These are all things
datawas implicitly exempt from (verified in hadron-server), so they become live once the body moves tocontent:contentandabstractare embedding sources (EmbedPlaintext { content?, abstract? }insrc/jobs/embedNodes.ts;Memory.embeddingSource=abstract | contentChunks | both) —datanever is. On a vector-indexed memory, every chat turn will now be chunked + embedded. That's the semantic-search upside above, but it's per-turn API cost and vector rows, so it's worth a conscious choice ofembeddingSourcefor chat memories.abstractOriginHashis compared againstcomputeContentHash(node.content). Messages have no abstract, so this should be benign — worth confirming no stale-abstract signal starts showing on message nodes.NodeRevision. Messages are append-only, so this should be negligible.contentgoes through the encryption path; confirmchat readrenders correctly (and that the fallback doesn't leak ciphertext into the transcript) on an encrypted chat memory.Docs to update alongside
hadron-docs/docs/how-to/agent-team-chat.md— "The data model" section shows the body insidedata.hadron-docs/docs/reference/node-types.md— mentions therecord/messagedistinction for high-volume content.hadronmemory.com::experiments::team-chat:academy:prompt-template) — these describe thedatapayload for hand-rolled messages.Note
While here:
hadron node ls --seq-gt/--sort-seqsilently hides the newest messages past the default 50-node page (#319) — the docs' "raw equivalent" snippet forchat readrecommends exactly that pattern, so it's worth correcting in the same docs pass.