**COMING SOON — BETA RELEASE NOT FOR PRODUCTION USE** The Headless SDK is currently under active development and is not yet available for beta use. We’ve published this documentation early to help you familiarize yourself with the SDK and prepare for the upcoming beta release.
Once the Headless SDK is available for beta, we’ll notify brands through our release notes.
When released as beta:
- The beta release is intended for early evaluation purposes only and is not recommended for production use.
- The SDK may contain bugs, errors, or incomplete or inaccurate information.
- APIs, features, and behavior are subject to change as we continue to develop and improve the SDK.
- Do not deploy or rely on the beta release in production environments.
Please note: The documentation, APIs, features, and examples may change before and during the beta period.
Lets an agent attach a free-text note to a conversation, visible to any agent who has (or later takes) the conversation. The SDK's wire contract is a single opaque string per conversation — it does not model note authorship, timestamps, or multiple entries. If your UI needs multiple, timestamped, attributed notes (one per agent-turn, not one shared field), you encode that structure into the string yourself, as shown below.
Clients: all examples use only the public MessagingClient API — client.on({ callback }) for
events and client.dispatch({ action, value }) for actions. dispatch() returns a Promise;
inline references below omit await for brevity, but always await it in real code.
Reading the current note
What it does: Surfaces a conversation's note with no separate fetch — it rides along with the conversation data you're already consuming.
Instructions:
client.on({
callback: (event) => {
if (event.type === 'conversations/update') {
const { conversation } = event.value;
console.log(conversation.note); // '' if no note has ever been set
}
},
});
Conversation.note is populated from the wire on every conversations/update notification — the
same mapping used for every other conversation field. A note set by any agent, including an echo
of the current agent's own write, surfaces automatically this way.
Writing a note
What it does: Overwrites the conversation's note with the given string.
Instructions:
await client.dispatch({
action: 'conversation/set-summary-note',
value: { conversationId, note: 'Consumer requesting a refund, escalate if unresolved by EOD.' },
});
- Resolves
voidonce UMS accepts the write; the updated value then arrives back through theconversations/updatestream (see above) rather than in thedispatch()resolution itself. - This is a full overwrite, not an append — the previous note string is replaced entirely. If you
need to preserve prior content, read the current
conversation.notefirst and fold it into the string you send (see the multi-note pattern below).
⚠️ Note:
dispatch()re-sends whatever string you give it as-is; the SDK does not parse, validate, or interpret its contents.
Pattern: multiple timestamped, attributed notes
Since the wire field is a single string, an agent workspace UI that wants one entry per agent-turn — author, timestamp, text — treats the string as an opaque JSON-encoded array it owns the shape of. The SDK has no awareness this string happens to contain JSON.
interface AgentNote {
agentId: string;
name: string;
noteContent: string;
noteId: string;
time: number;
}
function parseNotes(note: string | undefined): AgentNote[] {
if (!note) return [];
try {
const parsed = JSON.parse(note);
return Array.isArray(parsed) ? parsed : [];
} catch {
return [];
}
}
async function saveNote(conversationId: string, existingNote: string | undefined, agent: { id: string; name: string }, content: string) {
const notes = parseNotes(existingNote);
const lastNote = [...notes].sort((a, b) => b.time - a.time)[0];
if (lastNote?.agentId === agent.id) {
// Same agent revisiting their own last note: update it in place rather than appending.
const idx = notes.findIndex((n) => n.noteId === lastNote.noteId);
notes[idx] = { ...notes[idx], noteContent: content };
} else {
notes.push({
agentId: agent.id,
name: agent.name,
noteContent: content,
noteId: `${agent.id}::${Date.now()}`,
time: Date.now(),
});
}
await client.dispatch({
action: 'conversation/set-summary-note',
value: { conversationId, note: JSON.stringify(notes) },
});
}
- The whole array is re-serialized and re-sent on every save — there's no per-note server-side
patch. Always read
conversation.notefresh (not a stale local copy) immediately before building the array to save, to minimize the window for the next caveat.
⚠️ Note: there's no concurrency control. If two agents save around the same time, the second
set-summary-notedispatch wins and silently overwrites the first, since the entire array is replaced on every write. Acceptable for a lightweight notes UI; an integration needing stronger guarantees would need its own optimistic-concurrency layer (e.g. compare-and-swap on a version field inside the JSON) on top of this.
⚠️ Note: if an automated conversation summary feature is enabled for the account, it is expected to append an entry to this same array with an additional
isAutoSummary: truekey. Nothing in the SDK or the pattern above special-cases that key — such an entry renders like any other note unless your UI explicitly checks for it.
Enforcing the max note length
What it does: Site setting messaging.agent.notes.max.length caps how long a note string may
be; UMS does not reject over-length writes, so validate client-side if you want to warn the agent
before they lose content on submit.
Instructions:
const { maxAgentNotesLength } = session.permissions.configuration;
if (JSON.stringify(notes).length > maxAgentNotesLength) {
// warn the agent / truncate before dispatching
}
maxAgentNotesLength is read once at connect time as part of session.permissions.configuration
(see authentication-flow.md for the full SessionObject /
permissions shape). If you adopt the multi-note JSON pattern above, remember the length limit
applies to the serialized array as a whole, not to each individual note's text.