Skip to main content
A message has a role — user or assistant — and content. You post user messages; assistant messages are produced by runs.
The response is 202 with a run_id — the turn has started, not finished.

Attaching files

To hand the assistant a file, upload it first and pass its handle in file_ids:
The assistant sees the file, and can pass its handle to a file-reading tool like extract (wired as a patch). Attachments are PDFs or images, at most 10 MiB each, up to 10 per message. Files stay available for the life of the thread.

Posting resumes a paused run

If the thread has a run waiting on a question, your message is the answer and that run continues. If it has no paused run, a new run starts. You do not choose between these; the runtime does. The exception is a run waiting for a write approval. There, a message is refused:
Approve or reject first — see Confirmations.

Reading them

GET /v1/loom/threads/:thread_id/messages returns the thread’s messages in order. For what the agent did rather than what it said, read the trace instead.

Titles

The first user message becomes the thread’s title, truncated to 80 characters, for your history list. It is set once and not revised.

Streaming

If the agent has streaming enabled and realtime is configured, assistant content arrives as message.delta events during the run, then a final message.completed. Without realtime, poll the run and read the message when it closes.

Rich messages

An assistant reply can carry structured data alongside its text. When it does, the message has a parts array — an ordered list of text parts and data parts. A data part is { "type": "data", "kind", "payload", "ref" }: Patchwork sends the kind and a structured payload, and you render it — a chart, a table, a card, whatever your UI does with that kind. We never send markup or images.
content is always the plain-text fallback, so a client that doesn’t recognise a kind still shows something. A block reaches realtime subscribers as a message.block event; on reload it is on the message itself. Two things produce a data part: a patch tagged render: { kind } (its result becomes the block) and the native render_* tools the model can call. Rendering and any interactivity are yours — if a block needs to tell the agent something, just send a normal message.