Skip to content

chat: store the message body in Node.content instead of data.body #320

Description

@shadowbrush

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions