Skip to main content

Documentation Index

Fetch the complete documentation index at: https://www.plain.com/docs/llms.txt

Use this file to discover all available pages before exploring further.

Most agents need to read the contents of a thread before they decide what to do, whether that’s classifying it, adding a label, summarising it into a note, or generating a reply. This page covers how to read thread context via the GraphQL API.

Get a thread

The thread query returns a thread by ID along with its metadata. It throws if the thread doesn’t exist. This operation requires the thread:read permission.
const thread = await plain.query.thread({
  threadId: "th_01H8H46YPB2S4MAJM382FG9423",
});

console.log(thread.title);
console.log(thread.status);
console.log(thread.priority);
Most thread fields are scalars on the model. Related objects (customer, assignee, labels, …) are lazy-loaded, so accessing them triggers a separate API call. See the GraphQL SDK for more on how this works.

Get the thread content as LLM text

Each entry in a thread’s timeline (an inbound message, an outbound reply, a note, an assignment change, a label change) exposes an llmText field. This is a plain-text rendering of the entry shaped for feeding into a language model. To get the full thread as LLM-ready text, paginate through timelineEntries and concatenate llmText:
async function getThreadAsLlmText(threadId: string): Promise<string> {
  const thread = await plain.query.thread({ threadId });
  const parts: string[] = [];

  let page = await thread.timelineEntries({ first: 50 });
  while (true) {
    for (const entry of page.nodes) {
      if (entry.llmText) parts.push(entry.llmText);
    }

    const next = await page.fetchNext();
    if (!next) break;
    page = next;
  }

  return parts.join("\n\n");
}
The result is a single string with every meaningful event in the thread in chronological order, ready to drop into a prompt:
const context = await getThreadAsLlmText(thread.id);

const reply = await myModel.generate({
  system: "You are a customer support agent.",
  prompt: `Conversation so far:\n\n${context}\n\nWrite the next reply.`,
});
llmText is null for entry types where there’s nothing meaningful to render. Skip those entries.

Reading individual fields

If you don’t need the whole thread, you can read specific data directly. Some common patterns:
  • The customer: use thread.customer, or plain.query.customer({ customerId }). Useful for looking up subscription tier, external IDs, or anything else attached to the customer.
  • Custom thread fields: read structured data attached to the thread. See thread fields.
  • The triggering message: for events like thread.email_received or thread.chat_received, the message itself is on the webhook payload. No extra API call needed if that’s all you want.