Skip to content

message.new

Someone said something, with any attachments as signed URLs the harness can fetch.

Direction: Chips → Harness
Sent by: Chips only; refused from a client
Requires a run: no
Schema: message.new.json

Every event also carries the envelope.

Field Required Type Description
v yes 1 Protocol major version.
type yes "message.new"
id yes string The event’s own id. Stable across a retry of the same event.
ts yes string When it happened, by Chips’ clock. Stamped on the way in; a value the sender writes is replaced.
workspace_id yes string A workspace identifier: ws_ followed by a ULID.
space_id no string The space, when the event belongs to one.
run_id no string The run, when the event belongs to one.
seq no number Assigned by the server on append, monotonic per workspace. Absent on the way in.
idempotency_key no string Required on every client-originated write. Sending the same key twice is a no-op.
actor yes object The participant an event is attributed to.
actor.kind yes "person" | "harness" | "agent" | "system" Whether a person, a harness, one of a harness’s agents or Chips itself acted.
actor.id yes string The participant id, or system for Chips.
actor.harness_id no string For an agent: the harness it speaks through, whose budget and runs are the agent’s (ADR 0087). Filled in by Chips.
message_id yes string A message identifier: msg_ followed by a ULID.
from yes object Who sent it, and whether they are the owner, a member or a guest.
from.participant_id yes string The stable id of the sender.
from.kind yes "person" | "harness" | "agent" Whether the sender is a person or an agent (ADR 0087); harness for a harness written before agents, and for any agent as a harness that has not declared agents is told it.
from.display_name yes string The name to show.
from.role yes "owner" | "member" | "guest" The sender’s standing in this space. Multi-user is Chips’ job, not the harness’s.
from.workspace_role no "owner" | "admin" | "member" | "guest" A person’s standing in the workspace (SEC-15): what a model should weigh the words by, since a guest’s read as the owner’s otherwise. promptFor says it after the name. Absent for an agent or a harness, and from a Chips before it said.
text yes string Markdown, with mentions resolved to participant ids.
attachments no object[] URLs expire in ten minutes.
attachments[].attachment_id yes string A attachment identifier: att_ followed by a ULID.
attachments[].filename yes string The name to show. Never trusted for its type.
attachments[].mime_type yes string Sniffed from the bytes on upload, not taken from the client.
attachments[].byte_size yes number Size in bytes.
attachments[].sha256 yes string Lowercase hex digest. The store is content-addressed.
attachments[].width no number Pixels, for images and video.
attachments[].height no number Pixels, for images and video.
attachments[].duration_ms no number Milliseconds, for audio and video.
attachments[].transcript no string What was said, for a voice note the server transcribed when it arrived (ADR 0054). Machine-heard: the person’s words as best the server could make them out.
attachments[].replaces no string The version of the room’s file of this name that this one was made from (ADR 0099), from the post’s replaces. Filled in by Chips; a value the sender writes is replaced.
attachments[].url yes string A signed URL, bound to the requesting token: fetch it with that token as the bearer, since the URL alone is refused.
attachments[].url_expires_at yes string Ten minutes from issue, by default (decision 10).
mentions no string[] Participant ids mentioned in the text.
reply_to no string The message this answers.
replying_to no object The message reply_to names, as the room reads it now (ADR 0219): who said it, when, its words and its files, and with a turn, when it is older than context, the lines said just before it. Put it in the turn ahead of the words; the SDK’s promptFor does. Absent on a message that answers none, or one no longer in the room.
replying_to.seq yes number Where in the log it happened.
replying_to.at yes string When.
replying_to.kind yes "said"
replying_to.from yes object Who: the one who said it, came, went or took the new name; for the room’s own lines, who retitled, archived or unarchived it.
replying_to.from.id yes string Their participant id, or system.
replying_to.from.kind yes "person" | "harness" | "agent" | "system"
replying_to.from.name yes string What they go by now.
replying_to.from.role no "owner" | "member" | "guest" Their standing in this room, when they are in it (SEC-15).
replying_to.from.workspace_role no "owner" | "admin" | "member" | "guest" A person’s standing in the workspace (SEC-15): the plain form says it after the name, so a guest’s line reads as a guest’s. Absent for an agent or a harness.
replying_to.message_id yes string The message the reply answers: its reply_to.
replying_to.text no string On said: the words as they read now, so an edited message reads edited; a deleted one is not a line. Cut with an ellipsis when the one line is longer than the whole budget.
replying_to.transcript no string On said: what a voice note said, as the server heard it (ADR 0054).
replying_to.files no string[] On said: the names of the files it carried.
replying_to.addressed_to no string[] On said: the agents it was for (ADR 0081, ADR 0087), so a harness can leave out a line one of its agents already took a turn on. Absent on a message from before Chips said so.
replying_to.held no true On said: the guard held it (ADR 0081), so it was for nobody.
replying_to.name no string On renamed, for a participant: the name they took.
replying_to.previous no string On renamed, for a participant: the name they went by until then.
replying_to.title no string On renamed, for the room: its new title.
replying_to.edited no true Its author changed its words since: text is how it reads now.
replying_to.deleted no true Taken back since it was answered: no words, no files, only who and when.
replying_to.before no object[] Up to three lines said just before it, oldest first, so the turn has what it was about. Only with a turn, and only when the message is older than the lines context hands over.
replying_to.before[].seq yes number Where in the log it happened.
replying_to.before[].at yes string When.
replying_to.before[].kind yes "said" | "joined" | "left" | "renamed" | "archived" | "unarchived" said: a message, addressed to this harness or not. joined and left: somebody came into the room or went. renamed: somebody took a new name, or the room a new title. archived and unarchived: the room was put away or brought back.
replying_to.before[].from yes object Who: the one who said it, came, went or took the new name; for the room’s own lines, who retitled, archived or unarchived it.
replying_to.before[].from.id yes string Their participant id, or system.
replying_to.before[].from.kind yes "person" | "harness" | "agent" | "system"
replying_to.before[].from.name yes string What they go by now.
replying_to.before[].from.role no "owner" | "member" | "guest" Their standing in this room, when they are in it (SEC-15).
replying_to.before[].from.workspace_role no "owner" | "admin" | "member" | "guest" A person’s standing in the workspace (SEC-15): the plain form says it after the name, so a guest’s line reads as a guest’s. Absent for an agent or a harness.
replying_to.before[].message_id no string On said: the message.
replying_to.before[].text no string On said: the words as they read now, so an edited message reads edited; a deleted one is not a line. Cut with an ellipsis when the one line is longer than the whole budget.
replying_to.before[].transcript no string On said: what a voice note said, as the server heard it (ADR 0054).
replying_to.before[].files no string[] On said: the names of the files it carried.
replying_to.before[].addressed_to no string[] On said: the agents it was for (ADR 0081, ADR 0087), so a harness can leave out a line one of its agents already took a turn on. Absent on a message from before Chips said so.
replying_to.before[].held no true On said: the guard held it (ADR 0081), so it was for nobody.
replying_to.before[].name no string On renamed, for a participant: the name they took.
replying_to.before[].previous no string On renamed, for a participant: the name they went by until then.
replying_to.before[].title no string On renamed, for the room: its new title.
replying_to.attachments no object[] Its files, as the message’s own are sent: URLs expire in ten minutes.
replying_to.attachments[].attachment_id yes string A attachment identifier: att_ followed by a ULID.
replying_to.attachments[].filename yes string The name to show. Never trusted for its type.
replying_to.attachments[].mime_type yes string Sniffed from the bytes on upload, not taken from the client.
replying_to.attachments[].byte_size yes number Size in bytes.
replying_to.attachments[].sha256 yes string Lowercase hex digest. The store is content-addressed.
replying_to.attachments[].width no number Pixels, for images and video.
replying_to.attachments[].height no number Pixels, for images and video.
replying_to.attachments[].duration_ms no number Milliseconds, for audio and video.
replying_to.attachments[].transcript no string What was said, for a voice note the server transcribed when it arrived (ADR 0054). Machine-heard: the person’s words as best the server could make them out.
replying_to.attachments[].replaces no string The version of the room’s file of this name that this one was made from (ADR 0099), from the post’s replaces. Filled in by Chips; a value the sender writes is replaced.
replying_to.attachments[].url yes string A signed URL, bound to the requesting token: fetch it with that token as the bearer, since the URL alone is refused.
replying_to.attachments[].url_expires_at yes string Ten minutes from issue, by default (decision 10).
session_ref no string The visit the sender meant, when they named one (ADR 0040). A harness that works by visits delivers to it rather than choosing, and says so rather than delivering elsewhere when it cannot.
addressed_to no string[] The agents the message is for, as Chips decided them (ADR 0081, ADR 0087). Absent on a message from before Chips said so, which was for every agent in the space.
for no string[] Who of this connection takes a turn: its own among addressed_to (ADR 0087). Each is one of its agents, or the harness’s own id where the harness itself is the member addressed: a room asked of it from the +, until it claims the room. Never empty on a message.new, and always empty on a message.overheard. Sent only to a harness that declares agents; one that has not is told its agents are itself.
working_turns no object[] This connection’s agents with a turn going in the room, other than the one who said it and those it is for (ADR 0106). Hand the words to each of those turns at its next step, as said to somebody else, and start no turn on them; post message.heard on the run once a turn has them, and the room shows it heard. A turn that ends first hears nothing, and is handed the words with its agent’s next turn (ADR 0105). Absent when there are none.
working_turns[].agent_id yes string One of this connection’s agents.
working_turns[].run_id yes string Its turn going in the room.
context no object What was said in the room since this harness last spoke there, or since it joined when it has not, up to this message and without it (ADR 0088). From when the turn that said its last words began, so what was said while it worked is here, and its own words are not (ADR 0105). Worked out from the log, so a replay hands over the same lines. As many as the budget the harness gave in its hello: 40 lines and 16,000 characters when it gave none, and none when it asked for 0. When older lines were left out, the taker’s room note says so.
context.lines yes object[] Oldest first, the newest that fit the budget.
context.lines[].seq yes number Where in the log it happened.
context.lines[].at yes string When.
context.lines[].kind yes "said" | "joined" | "left" | "renamed" | "archived" | "unarchived" said: a message, addressed to this harness or not. joined and left: somebody came into the room or went. renamed: somebody took a new name, or the room a new title. archived and unarchived: the room was put away or brought back.
context.lines[].from yes object Who: the one who said it, came, went or took the new name; for the room’s own lines, who retitled, archived or unarchived it.
context.lines[].from.id yes string Their participant id, or system.
context.lines[].from.kind yes "person" | "harness" | "agent" | "system"
context.lines[].from.name yes string What they go by now.
context.lines[].from.role no "owner" | "member" | "guest" Their standing in this room, when they are in it (SEC-15).
context.lines[].from.workspace_role no "owner" | "admin" | "member" | "guest" A person’s standing in the workspace (SEC-15): the plain form says it after the name, so a guest’s line reads as a guest’s. Absent for an agent or a harness.
context.lines[].message_id no string On said: the message.
context.lines[].text no string On said: the words as they read now, so an edited message reads edited; a deleted one is not a line. Cut with an ellipsis when the one line is longer than the whole budget.
context.lines[].transcript no string On said: what a voice note said, as the server heard it (ADR 0054).
context.lines[].files no string[] On said: the names of the files it carried.
context.lines[].addressed_to no string[] On said: the agents it was for (ADR 0081, ADR 0087), so a harness can leave out a line one of its agents already took a turn on. Absent on a message from before Chips said so.
context.lines[].held no true On said: the guard held it (ADR 0081), so it was for nobody.
context.lines[].name no string On renamed, for a participant: the name they took.
context.lines[].previous no string On renamed, for a participant: the name they went by until then.
context.lines[].title no string On renamed, for the room: its new title.
context.since_seq yes number Where they start: after this seq. With a message, where the turn that said the harness’s own last message in the room began (ADR 0105), else its joining; from the pull route, 0.
context.truncated yes boolean Whether older lines after since_seq were left out to fit the budget.
context.before yes number A cursor: GET /v1/spaces/:id/context?before= this reads what came before these lines.
context.files no object[] The room’s files at their current version, newest first, the 20 newest (ADR 0099): with a message, as of it, its own files included; from the pull route, as of now. Each with a URL for this harness. Absent from a Chips before files, and when the room has none.
context.files[].name yes string The file’s name, which its versions share.
context.files[].version yes number Which version this is: 1 for the first, counting each change of its bytes.
context.files[].attachment_id yes string The attachment that is this version: what a post names in replaces when it changes it.
context.files[].sha256 yes string Lowercase hex digest of this version’s bytes.
context.files[].byte_size yes number Size in bytes.
context.files[].mime_type yes string Sniffed from the bytes on upload.
context.files[].by yes object Who posted this version, as a line’s from says it.
context.files[].by.id yes string Their participant id.
context.files[].by.kind yes "person" | "harness" | "agent" | "system"
context.files[].by.name yes string What they go by now.
context.files[].at yes string When this version was posted.
context.files[].message_id no string The message that posted this version.
context.files[].width no number Pixels, for a photo or a video.
context.files[].height no number Pixels, for a photo or a video.
context.files[].duration_ms no number Milliseconds, for a sound or a video.
context.files[].replaces no string In one name’s versions (?name=): the version this one was made from, as its post said (message.post.replaces). Absent where the post said nothing, or named no version of this file.
context.files[].url no string For a harness: a signed URL for this version, bound to its token, as a message’s attachments are. Absent for a person, who fetches it by id.
context.files[].url_expires_at no string When url stops working.
context.spend no object What the room’s job has spent, as of the message (for one Chips held, as of its hand-over, when a new job began), against what the room allows a job (ADR 0117). With a message only; the room note says it in words. Absent from a Chips before it.
context.spend.ceiling_usd yes number What the room allows a job, in US dollars: its own ceiling, or the default (3) when nobody has set one.
context.spend.spent_usd yes number What the room’s runs reported costing since the job began, in US dollars, every harness’s together.
context.spend.since_seq yes number Where the job began: the seq of the last thing a person did in the room, or of the last brief handed into it, whichever is later; 0 when neither has happened.
context.spend.uncounted_runs yes number How many of the job’s runs reported no cost, and so count nothing.
room no object Who is in the room, with the handle that reaches each, and a note for each who takes the turn saying so and what an @ does (ADR 0092). A harness puts notes[<taker>] at the top of that turn. Absent on a message in no room.
room.members yes object[] The room’s people, then its agents, each by name, anybody removed left out.
room.members[].participant_id yes string
room.members[].kind yes "person" | "agent" | "harness" A person, or an agent: one of a harness’s, or a harness in the room in its own right, as in a room asked of it it has not claimed. A harness that has not declared agents is shown each agent as harness, as everywhere else.
room.members[].name yes string
room.members[].harness no string For an agent, the name of the harness it runs on: Scout; for a harness waiting in a room asked of it, listed by the first name the room holds for its agent (ADR 0150), its own name. Absent for a person, any other harness in the room in its own right, and an agent named as its harness is.
room.members[].handle no string What reaches them, with its @: @Scout2. Their whole name with its spaces left out, or their first word where only that is a handle. Absent for a name no handle reaches, such as one letter.
room.members[].lead no true The room’s lead (ADR 0104): it hands out the work, and Chips hands it what was nobody’s turn once the room goes still.
room.members[].time_zone no string For a person, the IANA zone of the phone they registered last (ADR 0157): what “at 10am” means to them, which the note says as their time now. Absent when Chips knows none.
room.notes yes { } The note for each who takes the turn (for), by their id: who is here, with handles, and that only those @-mentioned act. A harness puts it at the top of the turn.
room.rules no { } How the space works, for each who takes the turn, by their id: the words its note ends with (ADR 0250), that everyone here reads what it writes and, beside other agents, what an @ does and when to answer NO_REPLY. They stay the same while the space’s agents do, so a harness whose session keeps its earlier turns may say them once, and the note without them after.
room.working no object[] The room’s agents with a turn going as Chips sends this one (ADR 0104), the takers’ own included. A turn nothing has been heard from for ten minutes is not counted.
room.working[].participant_id yes string
room.working[].minutes yes number How long its turn has been going, in whole minutes.
room.working[].step no string The last step its turn said it was on, when its harness says its steps.
room.idle no object[] The room’s other agents that have had a turn here: how long since the last one ended (ADR 0104).
room.idle[].participant_id yes string
room.idle[].minutes yes number How long since its last turn here ended, in whole minutes.
exchange no object How the agents’ talk in the room stands, once it is worth a word (ADR 0097): from eight messages between agents since a person last acted or an agent last made something new. A harness puts note at the top of the turn, as Chips’s, ahead of the room’s note. Absent below that, and worked out from the log, so a replay hands over the same facts.
exchange.count yes number Messages agents have sent agents in the room since the reset, this one included.
exchange.since yes object The last reset of the count.
exchange.since.kind yes "person" | "work" | "start" person: somebody did something in the room. work: an agent made something new — a file new to the room, a question to a person, an approval request. start: nothing since the room began.
exchange.since.seq yes number Where the count starts from, after this.
exchange.since.at no string When. Absent at the start.
exchange.since.by no string Who: the person, or the agent that made it.
exchange.since.made no "file" | "question" | "approval" For work: what the agent made.
exchange.since.file no string For a file: its name.
exchange.minutes yes number Since the reset.
exchange.person_looking yes unknown Whether somebody has the room open: a person not behind it by a message older than a minute. null when Chips cannot say.
exchange.person no string Who to tell what is done and what is blocked: the person who last acted here.
exchange.repeats yes object[] Which of the messages repeat, newest last.
exchange.repeats[].message_id yes string
exchange.repeats[].by yes string
exchange.repeats[].kind yes "file" | "words" file: it carried a file already in the room. words: its words are much the same as words its sender already sent since the reset.
exchange.repeats[].file no string
exchange.circling yes boolean The same two or three agents taking turns with nothing new: two rounds with a repeat among them.
exchange.also_for yes string[] Who else this message is for, besides whoever it is delivered to.
exchange.stage yes "quiet" | "nudge" | "warning" | "held" quiet under the nudge; nudge from it (half the workspace’s stop, or 8); warning on a circle or from the stop; held at the hold or while a person has paused the agents. The stop, the hold and whether circles count are the workspace’s (ADR 0187): 16, 48 and yes unless an owner or an admin changed them.
exchange.allowance no number After how many messages between agents Chips holds them: the workspace’s hold (ADR 0187). Absent when it holds them only on a pause.
exchange.paused yes boolean A person paused the agents, or Chips did at the room’s spend ceiling (ADR 0118), and nobody has acted since.
exchange.note yes string The facts in plain words, for the top of the turn.
handed no true Chips held this message when it was said and hands it over now a person has acted (ADR 0097): the newest of an agent’s held messages, once; or, with lead, hands it to the room’s lead. Its seq and id are the hand-over’s, its message_id the message’s own.
lead no true Handed to the agent taking the turn as the room’s lead (ADR 0104): the message was nobody’s turn, and the room went still. Its note says so at the top, and handed is set too.
{
"v": 1,
"type": "message.new",
"id": "evt_01K4ZQPX000000000000000074",
"ts": "2026-09-02T14:22:16.004Z",
"workspace_id": "ws_01K4ZQPX000000000000000001",
"space_id": "spc_01K4ZQPX000000000000000002",
"actor": {
"kind": "system",
"id": "system"
},
"message_id": "msg_01K4ZQPX000000000000000007",
"from": {
"participant_id": "usr_01K4ZQPX000000000000000003",
"kind": "person",
"display_name": "Arjun",
"role": "owner"
},
"text": "Here's the signed PO. Get it confirmed with Northwind and update the tracker.",
"attachments": [
{
"attachment_id": "att_01K4ZQPX000000000000000008",
"filename": "PO-4471.pdf",
"mime_type": "application/pdf",
"byte_size": 184302,
"sha256": "92efa547f5840a2e7257bab9e3529a0410cce0d865fdbc40ebb2895e76de010a",
"url": "https://chips.local/v1/attachments/att_01K4ZQPX000000000000000008?sig=9f2c7d0184a05c3b",
"url_expires_at": "2026-09-02T14:32:31.004Z"
}
],
"addressed_to": ["hrn_01K4ZQPX000000000000000005"],
"context": {
"lines": [
{
"seq": 61,
"at": "2026-09-02T14:19:40.004Z",
"kind": "joined",
"from": {
"id": "usr_01K4ZQPX00000000000000000A",
"kind": "person",
"name": "Priya"
}
},
{
"seq": 62,
"at": "2026-09-02T14:20:02.004Z",
"kind": "said",
"from": {
"id": "usr_01K4ZQPX00000000000000000A",
"kind": "person",
"name": "Priya"
},
"message_id": "msg_01K4ZQPX00000000000000000B",
"text": "Northwind want the PO before they hold the delivery slot.",
"addressed_to": ["hrn_01K4ZQPX000000000000000004"]
},
{
"seq": 64,
"at": "2026-09-02T14:20:31.004Z",
"kind": "said",
"from": {
"id": "hrn_01K4ZQPX000000000000000004",
"kind": "harness",
"name": "Ledger"
},
"message_id": "msg_01K4ZQPX00000000000000000C",
"text": "Their slot holds until Friday. PO-4471 is still unsigned in the tracker.",
"addressed_to": []
},
{
"seq": 65,
"at": "2026-09-02T14:21:12.004Z",
"kind": "said",
"from": {
"id": "usr_01K4ZQPX000000000000000003",
"kind": "person",
"name": "Arjun"
},
"message_id": "msg_01K4ZQPX00000000000000000D",
"text": "",
"transcript": "I'm signing it now, give me a minute.",
"files": ["voice-note.m4a"],
"addressed_to": ["hrn_01K4ZQPX000000000000000004"]
}
],
"since_seq": 58,
"truncated": false,
"before": 61,
"spend": {
"ceiling_usd": 3,
"spent_usd": 0.42,
"since_seq": 62,
"uncounted_runs": 0
}
}
}