Appearance
Conversations
Besides running workflows, an agent can hold a conversation: a person chats with an assistant, and the assistant answers with the same model loop a workflow step uses. It calls tools as that person and can start workflow runs for them. The assistant is defined by an assistant profile. Each conversation is a thread (a chat in the console).
Assistant profiles
A workflow step holds the instruction, tools and limits its model runs with, and an agent holds none of these. An assistant profile holds them for a conversation:
| Field | Meaning |
|---|---|
| Name, description | Name must be unique in the tenant (up to 128 characters). |
| Agent | The agent the assistant acts as. It must be Delegated or Orchestrator. An Autonomous agent acts without a user, so it is refused. |
| Model override | Optional provider and model. Without one, the agent's model is used. |
| Instruction | The system instruction (up to 20,000 characters). |
| Tools | Up to 50 tenant tools. Each one is Requires confirmation (the default) or Auto. |
| Skills | Up to 20 skills, as on a step: the latest or a pinned version, loaded Always or On demand. |
| Max rounds per turn | Model↔tool rounds one turn may run, 1–50 (default 10). |
| Token budget per turn | 1,000–1,000,000 tokens, or none (default 32,000). |
| History window | How many earlier turns are replayed to the model, 0–50 (default 20). |
| Requires purpose | Whether a chat must state its purpose before it starts. |
Profile tools default to Requires confirmation, while step tools default to Auto. This is deliberate: a chat replays earlier turns, so a tool runs without asking only if the author chose Auto for it. An Orchestrator agent's workflow tools come from the agent and are not listed on the profile.
Lifecycle. A new profile is a Draft at version 1. Activate it (this checks the agent type again) so that chats can use it. Deactivate it to stop new turns, or delete it (soft delete, which frees the name). Every save that changes the content adds a new version. Earlier versions never change and are listed on the profile's page. Manage profiles on the Assistant profiles page (/assistant-profiles).
Threads and turns
A thread is one person's conversation with an active profile. A turn is one user message and the assistant's reply.
- Pinned version. A thread uses the profile version that was current when it started. Every turn requires the profile to still be Active and, if the profile requires a purpose, the thread to have one.
- Runs as the user. A turn runs inside the request, under the caller's own token. Tools receive tokens delegated from that user, exactly as a step does for the user who started a run. Nothing is stored for later, and the delegated grants are revoked when the turn ends.
- People only. A thread belongs to a signed-in person and runs as that person. A service or agent token, or a request with no token, is refused with 403
Thread.HumanSubjectRequired. - Private. Every endpoint sees only the caller's own threads. Another user's thread answers 404, even in the same tenant.
- One turn at a time. A message sent while a turn is running answers 409
Thread.TurnInProgress. - Text-only history. The model gets the user message and final reply of the last history window completed turns. Earlier tool calls and tool output are not replayed.
- Streaming. Send the message with
Accept: text/event-streamto receive the turn as server-sent events:turn_started,text_delta,tool_call_started/tool_call_completed,workflow_run_started, and finallyturn_completed,confirmation_requiredorerror. The deltas are a preview; the reply inturn_completedis the final text. Tool arguments and results are never streamed. Proxies in front of the API must not buffertext/event-stream. - Lifecycle. Threads can be renamed, archived (read-only) or deleted. A thread's title defaults to the first 80 characters of its first message.
Because a turn runs inside the request, keep the profile's rounds and token budget low enough to finish within your gateway's request timeout.
Host context
An embedding host can send a few string key/value pairs with the thread and with each message, saying what the chat is about, for example { "fundId": "F-9" }. For each turn the message's pairs are applied over the thread's. The model reads them as its context, like a run's inputs, and read_context returns a value in full. The limits are 20 entries, keys of 1–64 characters from [A-Za-z0-9_.-], and values up to 2,000 characters (otherwise 400 Thread.HostContextInvalid). Host context is never used for authorization: treat it like a user message.
Workflows as tools
When the profile's agent is an Orchestrator, its workflow tools are offered in each turn, but only those whose workflow is active and that the user may run (run-workflow-instance). Calling one launches an ordinary run for the user, and the turn does not wait for it:
- The usual launch rules apply: launch policy, purpose, default agent, and version.
- The run's initial context is the tool inputs plus
thread_id,purposeandtask_id(the turn). - A repeated call with the same tool call id does not queue a second run.
- The model gets the run id straight away. The run's page links back to the thread.
The built-in read_run tool, offered only in chats, lets the model check one of the user's own runs: its state, its failure reason and, once it is done, its output. It never waits for a run to finish.
Tool confirmations in a chat
A call to a Requires confirmation tool pauses the turn (state AwaitingConfirmation) with the proposed arguments. The thread's owner decides it in the chat, not on the Confirmations page:
- Approve, optionally with edited arguments. The call runs and the same turn continues.
- Reject, with an optional reason. The model is told why and carries on without the call.
- Send a new message to decline the call. The paused turn ends
Cancelled.
A pending call expires after ToolConfirmation:TimeoutHours (72 hours by default). Clarifying questions (request_clarification) are never offered in a chat.
Cancelling a turn
Cancel a running or paused turn with POST /api/threads/{id}/turns/{turnId}/cancel (or Cancel in the chat). Closing the connection also cancels a running turn. The turn ends Cancelled, and anything it already recorded stays.
Cost
Every model and tool call a turn makes is recorded with the thread and turn. A turn reports its tokens and estimated cost, and the thread reports the totals over all its turns. The cost of a run that a chat launched stays on that run and is never added to the thread. Chats count towards usage limits, and a spent budget refuses a message with 403UsageLimit.BudgetExhausted. See Usage & Data Governance.
Turning it on and permissions
Threads ship off. While Threads:Enabled is false, every api/threads endpoint answers 404. The setting is read on every request.
| Permission | Allows |
|---|---|
start-thread | Starting a thread. |
view-threads, view-thread | Listing your threads, and reading one with its turns and conversation. |
send-thread-message | Sending messages, deciding confirmations, cancelling turns, renaming. |
archive-thread, delete-thread | Archiving and deleting your threads. |
The six thread keys belong to the agentfleet:thread-user role (and admin), not to viewer, because a turn calls tools as the user. The profile keys (view-assistant-profiles, view-assistant-profile, and create- / update- / activate- / deactivate- / delete-assistant-profile) are in viewer (the view keys), workflow-author and assistant-profile-admin.
In the console
- Chats (
/threads) lists your threads, with search and a status filter. - New chat (
/threads/new) asks for an active profile and, when needed, a purpose. - A chat (
/threads/{id}) streams replies, shows tool calls and launched runs, has inline Approve / Edit / Reject cards, and a Cancel button. - Conversation (
/threads/{id}/conversation) is a read-only view of every model round, showing the exact input the provider received.
From .NET
Agentfleet.Client.Http registers IAgentfleetThreadClient alongside the workflow client. SendMessageAsync returns the turn's events as an IAsyncEnumerable of typed events (ThreadTextDeltaEvent, ThreadWorkflowRunStartedEvent, ThreadConfirmationRequiredEvent, ThreadTurnCompletedEvent, …). SendMessageAndWaitAsync returns the finished turn instead. There are also approve, reject, cancel, rename, archive and delete methods. The client never retries a message, because a retry would start a second turn. It needs a person's token, for example an RFC 8693 token exchanged for the signed-in user.
Embedding in a portal
The API serves a framework-agnostic web component at /widget/agentfleet-chat.js. It renders in its own shadow root:
html
<script type="module" src="https://agentfleet.example.com/widget/agentfleet-chat.js"></script>
<agentfleet-chat api-base-url="https://agentfleet.example.com/api"
assistant-profile-id="6f1c…" host-context='{"fundId":"F-42"}'></agentfleet-chat>
<script type="module">
document.querySelector("agentfleet-chat").getToken =
({ forceRefresh }) => portal.getAgentfleetToken({ forceRefresh });
</script>- Attributes:
api-base-url,assistant-profile-idorthread-id(to resume a chat),purpose,host-context(sent with every message),run-url-template,heading,placeholder. getToken(required) returns the user's exchanged Agentfleet token. On a 401 the widget calls it once more withforceRefresh: true. It never sends cookies.- Events:
agentfleet-thread-started,agentfleet-turn-completed,agentfleet-run-started,agentfleet-confirmation-required,agentfleet-error.
The portal's origin must be listed in Cors:AllowedOrigins. For the full setup, see the embedding guide.
Related
- Agents: the identity a chat acts as.
- Runs: confirmations and runs launched from a chat.
- Usage & Data Governance: budgets and chat retention.
- Operator runbooks: threads and assistant profiles.