Chat

An agent is one definition. Nothing forces it to do both text and voice - a text agent needs no voice, no transcriber and no phone number, and a voice agent needs no chat widget. The prompt, the tools, the knowledge base and variables are shared by both. The differences are small and listed on those pages: a browser-only Client tool cannot run in chat, and in chat the knowledge base is searched on every message rather than when the model decides to.

TextVoice
What you build withthreads, or the browser chat widgetcalls, phoneNumbers, campaigns
How it is meteredper token, plus a flat fee per messageper token, per character and per minute by component, plus a flat platform fee per minute
Settings that applymodel, prompt, tools, knowledge, variablesthose, plus voice, transcriber and turn-taking

The same agent can do both if you want it to. See Pricing for how each is metered.

Threads and runs

A thread holds one conversation. A run is one turn on it: you add a user message, the agent replies, and the thread keeps the history so you never resend it.

A run completes before the call returns, so there is nothing to poll - the reply is already in the result. The thread carries the agent id it was opened against, so no later call has to repeat it.

import { Glytos } from '@glytos/node';

const glytos = new Glytos(process.env.GLYTOS_API_KEY!);

const thread = await glytos.threads.create({ agent: agentUuid });
const run = await glytos.threads.runs.create(thread, 'What are your opening hours?');

console.log(run.messages.at(-1)?.content);

A run returns status, current_node_id and messages, and messages holds only the agent's replies for that turn - not the message you sent, and not the earlier conversation. The whole conversation is on the thread:

const history = await glytos.threads.messages.list(thread);

Opening a thread with variables seeds it, and those values are available to the prompt for the rest of the conversation - see Variables.

Streaming

A long answer should not arrive as one silent wait. Streaming yields the reply as it is written, then a terminal done event carrying the finished turn. Routing, persistence and billing are identical either way.

for await (const event of glytos.threads.runs.stream(thread, 'Summarise the policy')) {
  if (event.type === 'token') process.stdout.write(event.delta);
  if (event.type === 'done') console.log(event.run.status);
}

Not every reply arrives token by token, so always render the reply from done, not only from the deltas you collected:

  • Only OpenAI models stream word by word. With other providers the whole reply arrives as a single token event just before done.
  • A turn in which the agent calls a tool does not stream at all: there are no token events and the reply is only in done.
  • A fixed message step in a workflow runs no model, so it has no token events either.

The stream is Server-Sent Events (POST /workflows/{agent}/sessions/{session}/messages/stream). Each frame is an event: line and a data: JSON line, followed by a blank line:

Eventdata
token{"delta": "..."} - the next piece of the reply.
done{"session_uuid", "status", "current_node_id", "messages"} - the finished turn. Unlike the non-streamed response, each message carries only role and content.
error{"message": "..."} - the turn failed.

A failure during the turn - a provider error, an exhausted balance - arrives as an error event inside an HTTP 200 response, because the response has already started. It carries a message but no error code. Authentication and request validation still fail with an ordinary HTTP error before the stream starts.

Per-turn instructions

Extra context that applies to one run only. It is placed in the system prompt ahead of the agent's own prompt, and is never saved to the agent, so a scoring pass or a one-off format demand does not permanently change how the agent behaves. Because the agent's prompt comes after it, write per-turn instructions so they do not depend on overriding something the prompt says later. Up to 32,000 characters.

await glytos.threads.runs.create(thread, {
  content: 'Rate this transcript',
  instructions: 'Score 1-5 and reply as JSON.',
});

The message may be empty when the instructions carry the turn: re-running a conversation with new instructions and no new message is a legitimate run. Such a turn adds only the agent's reply to the transcript.

Images and attachments

Images (data: URIs or URLs) ride on a single turn. A model without vision ignores them, though they are still stored with that message on the transcript:

await glytos.threads.runs.create(thread, {
  content: 'What is on this label?',
  images: [dataUri],
});

A file is attached to one conversation. Its text is put in front of the agent for that conversation only - it does not join the knowledge base and it does not reach any other thread. File upload is authenticated with a chat token, not your API key, so the same call works from a server or from the browser widget:

const { token } = await glytos.chat.token(agentUuid);

const file = await glytos.chat.uploadFile({
  token,
  sessionUuid: thread.id,
  file: buffer,
  filename: 'policy.txt',
});

What happens to the file:

  • Only its text is kept. PDFs with a text layer and UTF-8 text files are accepted; anything else is refused with 400 unsupported_document. The original file is not retained.
  • The upload limit is the same as for the knowledge base (10 MB unless your operator has changed it).
  • Up to 200,000 characters of text are stored per file; characters in the response is the length that was stored.
  • On each turn, at most 8,000 characters in total, across all of the conversation's files, reach the model. Files are included in upload order, so a large first file can leave no room for a later one.

For documents every conversation should be able to reach, or anything longer than a few pages, use the knowledge base instead.

In the browser

For a chat widget on your own site, use @glytos/web with a short-lived chat token you mint on your backend - never an API key.

Chat tokens

Your server mints a token with its API key, naming the agent, and hands the token to the page:

const { token, expires_in } = await glytos.chat.token(agentUuid);

The response is {"token", "workflow_uuid", "expires_in"}. The key needs permission to run agents.

  • A token is scoped to one agent and expires after expires_in seconds (one hour). Mint a fresh one when it expires; the conversation carries on under the new token with the same sessionUuid.
  • It authenticates the public chat endpoints (/chat/messages, /chat/stream, /chat/files) and nothing else: it can hold conversations with that one agent, but cannot call the rest of the API.
  • Each message is limited to 8,000 characters, and a token is rate-limited per minute; going faster returns 429 (or an error event when streaming).

The widget

import { GlytosChat } from '@glytos/web';

const chat = new GlytosChat({ token });

chat.on('token', t => append(t.delta));
chat.on('message', m => render(m));

await chat.start();
await chat.stream('Hello!');

The conversation id is on chat.sessionId; persist it and pass it back as sessionUuid to pick the same conversation up later.

Per-turn instructions are deliberately not available in the browser: the widget runs on a public page, so such a field would let anyone rewrite your agent's behaviour on your own site. Use a server SDK for that.

See the Web SDK.

Organising agents

Folders group agents inside one environment, so Development and Production organise independently:

const folder = await glytos.folders.create('Support');
await glytos.agents.moveToFolder(agentUuid, folder.uuid);

Deleting a folder deletes the agents filed in it, so the confirmation states the count. Their financial records survive; their own call history does not.

What it costs

Text is metered per token at the model's own rate, plus a small flat fee per message. A canned message step that runs no model is not billed. Bring your own provider key and the token cost is yours - only the flat fee applies. Two smaller items can appear beside a turn as usage: the embedding of each knowledge base search, and the summarization that keeps a long conversation within the model's context, when the agent has that enabled. See Pricing.

Where to look next

  • Tools - text runs the same saved tools voice does, except browser-only Client tools.
  • Sessions - every thread is a session, with its full transcript, cost breakdown and event log.
  • API reference - every endpoint.