diff --git a/chatkit/agents.py b/chatkit/agents.py
index b56c657..daddab5 100644
--- a/chatkit/agents.py
+++ b/chatkit/agents.py
@@ -110,6 +110,7 @@ class AgentContext(BaseModel, Generic[TContext]):
def generate_id(
self, type: StoreItemType, thread: ThreadMetadata | None = None
) -> str:
+ """Generate a new store-backed id for the given item type."""
if type == "thread":
return self.store.generate_thread_id(self.request_context)
return self.store.generate_item_id(
@@ -121,6 +122,7 @@ async def stream_widget(
widget: WidgetRoot | AsyncGenerator[WidgetRoot, None],
copy_text: str | None = None,
) -> None:
+ """Stream a widget into the thread by enqueueing widget events."""
async for event in stream_widget(
self.thread,
widget,
@@ -134,6 +136,7 @@ async def stream_widget(
async def end_workflow(
self, summary: WorkflowSummary | None = None, expanded: bool = False
) -> None:
+ """Finalize the active workflow item, optionally attaching a summary."""
if not self.workflow_item:
# No workflow to end
return
@@ -150,6 +153,7 @@ async def end_workflow(
self.workflow_item = None
async def start_workflow(self, workflow: Workflow) -> None:
+ """Begin streaming a new workflow item."""
self.workflow_item = WorkflowItem(
id=self.generate_id("workflow"),
created_at=datetime.now(),
@@ -161,9 +165,10 @@ async def start_workflow(self, workflow: Workflow) -> None:
# Defer sending added event until we have tasks
return
- await self.stream(ThreadItemAddedEvent(item=self.workflow_item))
+ await self.stream(ThreadItemAddedEvent(item=self.workflow_item))
async def update_workflow_task(self, task: Task, task_index: int) -> None:
+ """Update an existing workflow task and stream the delta."""
if self.workflow_item is None:
raise ValueError("Workflow is not set")
# ensure reference is updated in case task is a copy
@@ -179,6 +184,7 @@ async def update_workflow_task(self, task: Task, task_index: int) -> None:
)
async def add_workflow_task(self, task: Task) -> None:
+ """Append a workflow task and stream the appropriate event."""
self.workflow_item = self.workflow_item or WorkflowItem(
id=self.generate_id("workflow"),
created_at=datetime.now(),
@@ -202,6 +208,7 @@ async def add_workflow_task(self, task: Task) -> None:
)
async def stream(self, event: ThreadStreamEvent) -> None:
+ """Enqueue a ThreadStreamEvent for downstream processing."""
await self._events.put(event)
def _complete(self):
@@ -352,6 +359,7 @@ class StreamingThoughtTracker(BaseModel):
async def stream_agent_response(
context: AgentContext, result: RunResultStreaming
) -> AsyncIterator[ThreadStreamEvent]:
+ """Convert a streamed Agents SDK run into ChatKit ThreadStreamEvents."""
current_item_id = None
current_tool_call = None
ctx = context
@@ -1003,4 +1011,5 @@ async def to_agent_input(
def simple_to_agent_input(thread_items: Sequence[ThreadItem] | ThreadItem):
+ """Helper that converts thread items using the default ThreadItemConverter."""
return _DEFAULT_CONVERTER.to_agent_input(thread_items)
diff --git a/chatkit/server.py b/chatkit/server.py
index 71a9bb0..9d82b64 100644
--- a/chatkit/server.py
+++ b/chatkit/server.py
@@ -197,6 +197,7 @@ async def stream_widget(
copy_text: str | None = None,
generate_id: Callable[[StoreItemType], str] = default_generate_id,
) -> AsyncIterator[ThreadStreamEvent]:
+ """Stream a widget root (or async sequence of roots) as ThreadStreamEvents."""
item_id = generate_id("message")
if not isinstance(widget, AsyncGenerator):
@@ -277,6 +278,7 @@ def __init__(
store: Store[TContext],
attachment_store: AttachmentStore[TContext] | None = None,
):
+ """Create a ChatKitServer with the backing Store and optional AttachmentStore."""
self.store = store
self.attachment_store = attachment_store
@@ -314,6 +316,7 @@ async def add_feedback( # noqa: B027
feedback: FeedbackKind,
context: TContext,
) -> None:
+ """Persist user feedback for one or more thread items."""
pass
def action(
@@ -323,6 +326,7 @@ def action(
sender: WidgetItem | None,
context: TContext,
) -> AsyncIterator[ThreadStreamEvent]:
+ """Handle a widget or client-dispatched action and yield response events."""
raise NotImplementedError(
"The action() method must be overridden to react to actions. "
"See https://github.com/openai/chatkit-python/blob/main/docs/widgets.md#widget-actions"
@@ -381,6 +385,7 @@ async def handle_stream_cancelled(
async def process(
self, request: str | bytes | bytearray, context: TContext
) -> StreamingResult | NonStreamingResult:
+ """Parse an incoming ChatKit request and route it to streaming or non-streaming handlers."""
parsed_request = TypeAdapter[ChatKitReq](ChatKitReq).validate_json(request)
logger.info(f"Received request op: {parsed_request.type}")
diff --git a/chatkit/store.py b/chatkit/store.py
index 90be0b0..b091b86 100644
--- a/chatkit/store.py
+++ b/chatkit/store.py
@@ -48,11 +48,13 @@ class NotFoundError(Exception):
class AttachmentStore(ABC, Generic[TContext]):
@abstractmethod
async def delete_attachment(self, attachment_id: str, context: TContext) -> None:
+ """Delete an attachment by id."""
pass
async def create_attachment(
self, input: AttachmentCreateParams, context: TContext
) -> Attachment:
+ """Create an attachment record from upload metadata."""
raise NotImplementedError(
f"{type(self).__name__} must override create_attachment() to support two-phase file upload"
)
@@ -78,10 +80,12 @@ def generate_item_id(
@abstractmethod
async def load_thread(self, thread_id: str, context: TContext) -> ThreadMetadata:
+ """Load a thread's metadata by id."""
pass
@abstractmethod
async def save_thread(self, thread: ThreadMetadata, context: TContext) -> None:
+ """Persist thread metadata (title, status, etc.)."""
pass
@abstractmethod
@@ -93,20 +97,24 @@ async def load_thread_items(
order: str,
context: TContext,
) -> Page[ThreadItem]:
+ """Load a page of thread items with pagination controls."""
pass
@abstractmethod
async def save_attachment(self, attachment: Attachment, context: TContext) -> None:
+ """Persist attachment metadata."""
pass
@abstractmethod
async def load_attachment(
self, attachment_id: str, context: TContext
) -> Attachment:
+ """Load attachment metadata by id."""
pass
@abstractmethod
async def delete_attachment(self, attachment_id: str, context: TContext) -> None:
+ """Delete attachment metadata by id."""
pass
@abstractmethod
@@ -117,32 +125,38 @@ async def load_threads(
order: str,
context: TContext,
) -> Page[ThreadMetadata]:
+ """Load a page of threads with pagination controls."""
pass
@abstractmethod
async def add_thread_item(
self, thread_id: str, item: ThreadItem, context: TContext
) -> None:
+ """Persist a newly created thread item."""
pass
@abstractmethod
async def save_item(
self, thread_id: str, item: ThreadItem, context: TContext
) -> None:
+ """Upsert a thread item by id."""
pass
@abstractmethod
async def load_item(
self, thread_id: str, item_id: str, context: TContext
) -> ThreadItem:
+ """Load a thread item by id."""
pass
@abstractmethod
async def delete_thread(self, thread_id: str, context: TContext) -> None:
+ """Delete a thread and its items."""
pass
@abstractmethod
async def delete_thread_item(
self, thread_id: str, item_id: str, context: TContext
) -> None:
+ """Delete a thread item by id."""
pass
diff --git a/docs/concepts/actions.md b/docs/concepts/actions.md
new file mode 100644
index 0000000..11d4f36
--- /dev/null
+++ b/docs/concepts/actions.md
@@ -0,0 +1,22 @@
+# Actions
+
+ChatKit actions are interaction events triggered by widgets or client code that let the client and server run logic or start a model response independently of user messages.
+
+## Widget actions
+
+Widget actions are specified in the widget definition itself (for example, `Button.onClickAction`), so every interaction carries a typed action payload plus the widget item that fired it. By default actions are routed to the server, but you can set `handler: "client"` when you want to intercept the action in the browser first.
+
+### Server-handled actions
+
+If you leave the handler unset, the action is delivered to `ChatKitServer.action(thread, action, sender, context)`, where `sender` is the widget item that triggered it when that item is available. Server handling is the right choice when you need to mutate thread state, stream widget or message updates, or start an agent response without a new user message. Record important interactions as hidden context so the model can react on the next turn (for example, “user clicked confirm”), and treat `action.payload` as untrusted input that must be validated and authorized before you persist anything.
+
+### Client-handled actions
+
+When you set `handler: "client"`, the action flows into the client SDK’s `widgets.onAction` callback so you can do immediate UI work such as opening dialogs, navigating, or running local validation. Client handlers can still forward a follow-up action to the server with `chatkit.sendCustomAction()` after local logic finishes. The server thread stays unchanged unless you explicitly send that follow-up action or a message.
+
+## Client-sent actions using the chatkit.sendCustomAction() command
+
+Your client integration can also initiate actions directly with `chatkit.sendCustomAction(action, itemId?)`, optionally namespaced to a specific widget item. The server receives these in `ChatKitServer.action` just like a widget-triggered action and can stream widgets, messages, or client effects in response. This pattern is useful when a flow starts outside a widget—or after a client-handled action—but you still want the server to persist results or involve the model.
+
+## Related guides
+- [Handle widget actions](../guides/add-features/handle-widget-actions.md)
diff --git a/docs/concepts/entities.md b/docs/concepts/entities.md
new file mode 100644
index 0000000..bb5f111
--- /dev/null
+++ b/docs/concepts/entities.md
@@ -0,0 +1,30 @@
+# Entities
+
+Entities are structured pieces of information your system can recognize during a conversation, such as names, dates, IDs, or product-specific objects.
+
+They represent meaningful objects in your app’s domain. For example:
+
+- In a notes app, entities might be documents.
+- In a news site, they might be articles.
+- In an online store, they might be products.
+
+When referenced, entities can link messages to real data and power richer actions and previews.
+
+## Entity sources for assistant messages
+
+Entities can be used as cited sources in assistant responses.
+
+**References:**
+
+- The [EntitySource](../../api/chatkit/types/#chatkit.types.EntitySource) Pydantic model definition
+- [Add annotations in assistant messages](../guides/add-features/add-annotations.md#annotating-with-custom-entities).
+
+## Entity tags as @-mentions in user messages
+
+Users can tag your entities in the composer using @-mentions.
+
+**References**:
+
+- The [Entity](https://openai.github.io/chatkit-js/api/openai/chatkit-react/type-aliases/entity/) TypeScript type definition
+- The [UserMessageTagContent](../../api/chatkit/types/#chatkit.types.UserMessageTagContent) Pydantic model definition
+- [Allow @-mentions in user messages](../guides/add-features/allow-mentions.md).
diff --git a/docs/concepts/thread-items.md b/docs/concepts/thread-items.md
new file mode 100644
index 0000000..d4b3ad7
--- /dev/null
+++ b/docs/concepts/thread-items.md
@@ -0,0 +1,54 @@
+# Thread items
+
+Thread items are the individual records that make up a thread. This include user and assistant messages, widgets, workflows, and internal markers that guide processing. ChatKit orders and paginates them through your store implementation.
+
+They drive two core experiences:
+
+- **Model input**: Your server's [`respond`](../../api/chatkit/server/#chatkit.server.ChatKitServer.respond) logic will read items to build model input so the model sees the full conversation during an active turn and when resuming past threads. See [Compose model input](../guides/compose-model-input.md).
+- **UI rendering**: ChatKit.js renders items incrementally for the active thread during streaming, and re-renders the persisted items when past threads are loaded.
+
+## User messages
+
+[`UserMessageItem`](../../api/chatkit/types/#chatkit.types.UserMessageItem)s represent end-user input. A user message can include the entered text, optional `quoted_text` for reply-style UI, and attachment metadata. User text is plain (no Markdown rendering) but can include @-mentions/tags; see [Allow @-mentions in user messages](../guides/add-features/allow-mentions.md).
+
+## Assistant messages
+
+[`AssistantMessageItem`](../../api/chatkit/types/#chatkit.types.AssistantMessageItem)s represent assistant responses. Content can include text, tool call outputs, widgets, and annotations. Text is Markdown-rendered and can carry inline annotations; see [Add annotations in assistant messages](../guides/add-features/add-annotations.md).
+
+### Markdown support
+
+Markdown in assistant messages supports:
+
+- GitHub-flavored Markdown: Lists, headings, code fences, inline code, blockquotes, links—all with streaming-friendly layout.
+- Lists: Ordered/unordered lists stay stable while streaming (Safari-safe markers, no reflow glitches).
+- Line breaks: Single newlines render as `
` when `breakNewLines` is enabled.
+- Code blocks: Syntax-highlighted, copyable, and streamed smoothly; copy buttons are always present.
+- Math: LaTeX via remark/rehype math plugins for inline and block equations.
+- Tables: Automatic sizing with horizontal scroll for wide outputs.
+- Inline annotations: Markdown directives spawn interactive annotations wired into ChatKit handlers.
+
+## Hidden context items
+
+Hidden context items serve as model input but are not rendered in the chat UI. Use them to pass non-visible signals (for example, widget actions or system context) so the model can respond to what the user did, not just what they typed.
+
+- [`HiddenContextItem`](../../api/chatkit/types/#chatkit.types.HiddenContextItem): Your integration’s hidden context; you control the schema and how it is converted for the model.
+- [`SDKHiddenContextItem`](../../api/chatkit/types/#chatkit.types.SDKHiddenContextItem): Hidden context inserted by the ChatKit Python SDK for its own operations; you normally leave it alone unless you override conversion behavior.
+
+
+## ThreadItemConverter
+
+[`ThreadItemConverter`](../../api/chatkit/agents/#chatkit.agents.ThreadItemConverter) maps stored thread items into model-ready input items. Defaults cover messages, widgets, workflows, and tasks; override it to handle attachments, tags, or hidden context in the format your model expects. Combine converter tweaks with prompting so the model sees a coherent view of rich items (for example, summarizing widgets or tasks into text the model can consume).
+
+## Thread item actions
+
+Thread item actions are quick action buttons attached to an assistant turn that let users act on the output, such as retrying, copying, or submitting feedback.
+
+They can be configured client-side with the [threadItemActions option](https://openai.github.io/chatkit-js/api/openai/chatkit-react/type-aliases/threaditemactionsoption/).
+
+
+## Related guides
+- [Persist ChatKit threads and messages](../guides/persist-chatkit-data.md)
+- [Compose model inputs](../guides/compose-model-input.md)
+- [Add annotations in assistant messages](../guides/add-features/add-annotations.md)
+- [Allow @-mentions in user messages](../guides/add-features/allow-mentions.md)
+- [Handle feedback](../guides/add-features/handle-feedback.md)
\ No newline at end of file
diff --git a/docs/concepts/thread-stream-events.md b/docs/concepts/thread-stream-events.md
new file mode 100644
index 0000000..7940e0d
--- /dev/null
+++ b/docs/concepts/thread-stream-events.md
@@ -0,0 +1,46 @@
+# Thread stream events
+
+[`ThreadStreamEvent`](../../api/chatkit/types/#chatkit.types.ThreadStreamEvent)s are the Server-Sent Event (SSE) payloads streamed by ChatKitServer while responding to a user message or action. They keep the client UI in sync with server-side processing and drive persistence in your store.
+
+## Thread metadata updates
+
+ChatKitServer emits these after it creates a thread or notices metadata changes (title, status, etc.) so the UI stays in sync.
+
+- [`ThreadCreatedEvent`](../../api/chatkit/types/#chatkit.types.ThreadCreatedEvent): introduce a new thread
+- [`ThreadUpdatedEvent`](../../api/chatkit/types/#chatkit.types.ThreadUpdatedEvent): update the current thread metadata such as title or status
+
+## Thread item events
+
+Thread item events drive the conversation state. ChatKitServer processes these events to persist conversation state before streaming them back to the client.
+
+- [`ThreadItemAddedEvent`](../../api/chatkit/types/#chatkit.types.ThreadItemAddedEvent): introduce a new item (message, tool call, workflow, widget, etc).
+- [`ThreadItemUpdatedEvent`](../../api/chatkit/types/#chatkit.types.ThreadItemUpdatedEvent): mutate a pending item (e.g., stream text deltas, workflow task updates).
+- [`ThreadItemDoneEvent`](../../api/chatkit/types/#chatkit.types.ThreadItemDoneEvent): mark an item complete and persist it.
+- [`ThreadItemRemovedEvent`](../../api/chatkit/types/#chatkit.types.ThreadItemRemovedEvent): delete an item by id.
+- [`ThreadItemReplacedEvent`](../../api/chatkit/types/#chatkit.types.ThreadItemReplacedEvent): swap an item in place.
+
+Note: `ThreadItemAddedEvent` does not persist the item. `ChatKitServer` saves on `ThreadItemDoneEvent`/`ThreadItemReplacedEvent`, tracks pending items in between, and handles store writes for all `ThreadItem*Event`s.
+
+## Errors
+
+Stream [`ErrorEvent`](../../api/chatkit/types/#chatkit.types.ErrorEvent)s for user-facing errors in the chat UI. You can configure a custom message and whether a retry button is shown to the user.
+
+## Progress updates
+
+Stream [`ProgressUpdateEvent`](../../api/chatkit/types/#chatkit.types.ProgressUpdateEvent)s to show the user transient status while work is in flight.
+
+See [Show progress for long-running tools](../guides/add-features/show-progress-for-long-running-tools.md) for more info.
+
+## Client effects
+
+Use [`ClientEffectEvent`](../../api/chatkit/types/#chatkit.types.ClientEffectEvent) to trigger fire-and-forget behavior on the client such as opening a dialog or pushing updates.
+
+See [Send client effects](../guides/add-features/send-client-effects.md) for more info.
+
+## Stream options
+
+[`StreamOptionsEvent`](../../api/chatkit/types/#chatkit.types.StreamOptionsEvent) configures runtime stream behavior (for example, allowing user cancellation). `ChatKitServer` emits one at the start of every stream using `get_stream_options`; override that method to change defaults such as `allow_cancel`.
+
+
+## Related guides
+- [Stream responses back to your user](../guides/stream-thread-events.md)
diff --git a/docs/concepts/threads.md b/docs/concepts/threads.md
new file mode 100644
index 0000000..2de12ee
--- /dev/null
+++ b/docs/concepts/threads.md
@@ -0,0 +1,16 @@
+# Threads
+
+Threads are the core unit of ChatKit: a single conversation timeline that groups messages, tool calls, widgets, and related metadata.
+
+## Lifecycle
+- When a user submits a message and no thread exists, `ChatKitServer` creates one by calling your store's [`save_thread`](../../api/chatkit/store/#chatkit.store.Store.save_thread).
+- As responses stream back, `ChatKitServer` automatically persists thread items as they are completed—see [Thread items](thread-items.md) and [Stream responses back to your user](../guides/stream-thread-events.md) for how events drive storage.
+- Update titles or metadata intentionally in your integration (e.g., after summarizing a topic) by calling [`store.save_thread`](../../api/chatkit/store/#chatkit.store.Store.save_thread) with the new values.
+- When history is enabled client-side, ChatKit retrieves past threads. The user can continue any previous thread by default.
+- Archive or close threads according to your policies: mark them read-only (e.g., [disable new messages](../guides/add-features/disable-new-messages.md)) or delete them if you no longer want them discoverable.
+
+
+## Related guides
+- [Persist ChatKit threads and messages](../guides/persist-chatkit-data.md)
+- [Save thread titles](../guides/add-features/save-thread-titles.md)
+- [Disable new messages for a thread](../guides/add-features/disable-new-messages.md)
diff --git a/docs/concepts/widgets.md b/docs/concepts/widgets.md
new file mode 100644
index 0000000..f0aa72f
--- /dev/null
+++ b/docs/concepts/widgets.md
@@ -0,0 +1,56 @@
+# Widgets
+
+Widgets are structured UI elements the assistant can stream into the conversation. They let you render forms, cards, lists, or other interactive components instead of plain text.
+
+## Representation and delivery
+
+Here’s how a widget is represented from design time through runtime streaming.
+
+| Stage | What it contains |
+| --- | --- |
+| Working definition | [Widget UI language](#widget-ui-language) plus a schema (Zod/JSON) and example data you author in . |
+| Published definition | The exported [`.widget` file](#widget-files) bundling the layout, schema, and sample data. |
+| Server runtime (definition only) | [`WidgetTemplate`](../../api/chatkit/widgets/#chatkit.widgets.WidgetTemplate) instance loaded from the `.widget` file. |
+| Server runtime (hydrated) | [`DynamicWidgetRoot`](../../api/chatkit/widgets/#chatkit.widgets.DynamicWidgetRoot) or [`BasicRoot`](../../api/chatkit/widgets/#chatkit.widgets.BasicRoot) Pydantic model instance built from the template and real data. |
+| Streamed to the client | The hydrated root serialized to JSON and included inside a [`WidgetItem`](../../api/chatkit/types/#chatkit.types.WidgetItem) streamed by `ChatKitServer`. |
+| Rendered by the client | ChatKit.js deserializes the JSON into typed widget objects (for example, [`Card`](https://openai.github.io/chatkit-js/api/openai/chatkit-react/namespaces/widgets/type-aliases/card/) or [`ListView`](https://openai.github.io/chatkit-js/api/openai/chatkit/namespaces/widgets/type-aliases/listview/)) and renders them; entity previews use [`BasicRoot`](https://openai.github.io/chatkit-js/api/openai/chatkit-react/namespaces/widgets/type-aliases/basicroot/) returned from [`entities.onRequestPreview`](#entity-previews). |
+| Sent as model input | A Responses API `Message` produced from a [`WidgetItem`](../../api/chatkit/types/#chatkit.types.WidgetItem) via [`ThreadItemConverter.widget_to_input`](../../api/chatkit/agents/#chatkit.agents.ThreadItemConverter.widget_to_input). |
+
+
+## Widget UI language
+
+Widget layouts use a strict, simplified JSX dialect that only allows specific components and props. Explore the available components and their props in to see what the renderer supports.
+
+### Containers
+
+Every widget must be wrapped in a root-level container element. For single, self-contained content such as a summary, confirmation, or form, use ``. For a set of options (for example, restaurants or files), use ``. Reserve `` for entity previews.
+
+- ``: Simple card with a light border and plain background; supports confirm and cancel actions.
+- ``: Scroll-friendly list with built-in “show more” mechanics. Children must be ``, and `` must only appear as a direct child of ``; it has a constrained prop set for row-like layout (`children`, `gap`, `align`, `onClickAction`).
+- ``: Minimal container only used for entity previews.
+
+## .widget files
+
+Exported `.widget` files are JSON blobs that include the widget template, the expected data schema, and supporting metadata. You can load them server-side and render widgets dynamically with `WidgetTemplate`; see [Build widgets with `WidgetTemplate`](../guides/add-features/stream-widgets.md#build-widgets-with-widgettemplate) for examples.
+
+## WidgetItem
+
+[`WidgetItem`](../../api/chatkit/types/#chatkit.types.WidgetItem) represents a widget rendered as a [thread item](thread-items.md) in the chat UI. In addition to a reference to the widget instance, it contains a `copy_text` field that represents the text value copied to the clipboard when the user clicks the copy button below the response.
+
+## Entity previews
+
+The [`entities.onRequestPreview`](https://openai.github.io/chatkit-js/api/openai/chatkit-react/type-aliases/entitiesoption/#onrequestpreview) ChatKit option returns a preview typed as [`BasicRoot`](https://openai.github.io/chatkit-js/api/openai/chatkit-react/namespaces/widgets/type-aliases/basicroot/).
+
+
+## When to use
+
+- Collect structured input (forms) or present rich results (tables, cards, charts) that text alone cannot convey.
+- Present the user with multiple choice options.
+- Pair with actions to let users submit selections, confirm steps, or trigger server-side work.
+- Mix with text to provide explanation plus an interactive control.
+
+## Related guides
+
+- [Stream widgets](../guides/add-features/stream-widgets.md)
+- [Create custom forms](../guides/add-features/create-custom-forms.md)
+- [Handle widget actions](../guides/add-features/handle-widget-actions.md)
diff --git a/docs/guides/convert-user-input.md b/docs/guides/compose-model-input.md
similarity index 98%
rename from docs/guides/convert-user-input.md
rename to docs/guides/compose-model-input.md
index 21b4a17..9f8fa23 100644
--- a/docs/guides/convert-user-input.md
+++ b/docs/guides/compose-model-input.md
@@ -1,4 +1,4 @@
-# Convert user input to model input
+# Compose model inputs
ChatKit delivers structured thread items (messages, tools, attachments). Before running inference, convert those items into the model's expected input format.
diff --git a/docs/guides/persist-chatkit-data.md b/docs/guides/persist-chatkit-data.md
index c553319..c1b51f6 100644
--- a/docs/guides/persist-chatkit-data.md
+++ b/docs/guides/persist-chatkit-data.md
@@ -114,4 +114,4 @@ thread.metadata["previous_response_id"] = result.response_id
## Next
-[Convert user input to model input](convert-user-input.md)
\ No newline at end of file
+[Compose model input](compose-model-input.md)
\ No newline at end of file
diff --git a/docs/guides/stream-thread-events.md b/docs/guides/stream-thread-events.md
index ce7ab9c..3adf9be 100644
--- a/docs/guides/stream-thread-events.md
+++ b/docs/guides/stream-thread-events.md
@@ -1,8 +1,10 @@
# Stream responses back to your user
-ChatKit.js listens for `ThreadStreamEvent`s over SSE. Stream events from `ChatKitServer.respond` so users see model output, tool activity, progress updates, and errors in real time.
+ChatKit.js listens for [`ThreadStreamEvent`](../../api/chatkit/types/#chatkit.types.ThreadStreamEvent)s over SSE. Stream events from [`ChatKitServer.respond`](../../api/chatkit/server/#chatkit.server.ChatKitServer.respond) so users see model output, tool activity, progress updates, and errors in real time.
-Thread events include both persistent thread items (messages, tools, workflows) that are saved to the conversation history, and non-persistent runtime signals (progress updates, notices, errors, and client effects) that show ephemeral UI or drive immediate client behavior without being stored.
+Thread stream events include both persistent thread items (messages, tools, workflows) that are saved to the conversation history, and non-persistent runtime signals (progress updates, notices, errors, and client effects) that show ephemeral UI or drive immediate client behavior without being stored.
+
+See [Thread stream events](../concepts/thread-stream-events.md) for an overview of supported event types.
### From `respond`
@@ -81,55 +83,8 @@ class MyChatKitServer(ChatKitServer[MyRequestContext]):
)
```
-When you stream thread events manually, remember that tools cannot `yield` events. If you skip `stream_agent_response`, you must merge any tool-emitted events yourself—for example, by reading from `AgentContext._events` (populated by `ctx.context.stream(...)` or workflow helpers) and interleaving them with your own `respond` events.
-
-## Event types at a glance
-
-Use these when emitting events directly (or alongside `stream_agent_response`). Thread lifecycle events become part of conversation history; the others are ephemeral runtime signals that shape client behavior but are not persisted.
-
-### Thread lifecycle events
-
-Thread item events drive the conversation state. ChatKitServer processes these events to persist conversation state before streaming them back to the client.
-
-- `ThreadItemAddedEvent`: introduce a new item (message, tool call, workflow, widget, etc).
-- `ThreadItemUpdatedEvent`: mutate a pending item (e.g., stream text deltas, workflow task updates).
-- `ThreadItemDoneEvent`: mark an item complete and persist it.
-- `ThreadItemRemovedEvent`: delete an item by id.
-- `ThreadItemReplacedEvent`: swap an item in place.
-
-Note: `ThreadItemAddedEvent` does not persist the item. `ChatKitServer` saves on `ThreadItemDoneEvent`/`ThreadItemReplacedEvent`, tracks pending items in between, and handles store writes for all `ThreadItem*Event`s.
-
-### Errors
-
-Stream an `ErrorEvent` for user-facing errors.
-
-```python
-async def respond(...) -> AsyncIterator[ThreadStreamEvent]:
- if not user_has_remaining_quota(context):
- yield ErrorEvent(
- message="You have reached your usage limit.",
- allow_retry=False,
- )
- return
-
- # Rest of your respond method
-```
-
-### Progress updates
-
-Stream `ProgressUpdateEvent` to show the user transient status while work is in flight.
-
-See [Show progress for long-running tools](add-features/show-progress-for-long-running-tools.md) for more info.
-
-### Client effects
-
-Use `ClientEffectEvent` to trigger fire-and-forget behavior on the client such as opening a dialog or pushing updates.
-
-See [Send client effects](add-features/send-client-effects.md) for more info.
-
-### Stream options
+When you stream events manually, remember that tools cannot `yield` events. If you skip `stream_agent_response`, you must merge any tool-emitted events yourself—for example, by reading from `AgentContext._events` (populated by `ctx.context.stream(...)` or workflow helpers) and interleaving them with your own `respond` events.
-`StreamOptionsEvent` configures runtime stream behavior (for example, allowing user cancellation). `ChatKitServer` emits one at the start of every stream using `get_stream_options`; override that method to change defaults such as `allow_cancel`.
## Next
diff --git a/mkdocs.yml b/mkdocs.yml
index cd15fe7..46580c5 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -4,9 +4,7 @@ theme:
features:
- content.code.copy
- content.code.select
- - navigation.sections
- navigation.path
- - navigation.expand
- content.code.annotate
palette:
primary: black
@@ -39,10 +37,17 @@ plugins:
nav:
- Home: index.md
+ - Concepts:
+ - Threads: concepts/threads.md
+ - Thread items: concepts/thread-items.md
+ - Thread stream events: concepts/thread-stream-events.md
+ - Widgets: concepts/widgets.md
+ - Actions: concepts/actions.md
+ - Entities: concepts/entities.md
- Guides:
- Serve ChatKit from your backend: guides/serve-chatkit.md
- Persist ChatKit threads and messages: guides/persist-chatkit-data.md
- - Convert user input to model input: guides/convert-user-input.md
+ - Compose model input: guides/compose-model-input.md
- Run inference: guides/run-inference.md
- Stream responses back to your user: guides/stream-thread-events.md
- Add features: