# SAALT > Complete documentation for Large Language Models --- ## Document: Introduction SAALT is an AI platform for building, running, and managing Agents. Start here — whether you use SAALT, build an App, call the API, or are an LLM reading these docs. URL: /introduction # Introduction SAALT is an AI platform for building, running, and managing Agents. You work with your Agents through the web app or programmatically, and you can extend the platform with your own Apps and integrations. **Where to start:** - **Using SAALT?** Begin with [Getting started](/user-guide/getting-started) and the User Guide. - **Building an App?** See the [Apps overview](/apps/overview). - **Calling the API?** See the [API overview](/api/overview). - **An LLM or agent reading these docs?** Every page is also available as Markdown — append `.md` to any URL — and the site publishes [`/llms.txt`](/llms.txt) and [`/llms-full.txt`](/llms-full.txt) for ingestion. ## Core concepts - **[Spaces](/user-guide/spaces)** — workspaces that organize your Agents, Knowledge, and Apps. Spaces can be shared with a team or kept as private, personal Spaces. - **[Agents](/user-guide/agents)** — configurable AI assistants with their own model, instructions, Knowledge, and Tools. - **[Chat](/user-guide/chat)** — the conversational interface to an Agent, with an Agent switcher, file uploads, conversation search, and deep-research progress. - **[Knowledge](/user-guide/knowledge)** — the documents and sources an Agent can draw on. Each Knowledge Document is indexed so the Agent can ground its answers. - **[Memory](/user-guide/memory)** — personal, per-user context an Agent remembers across conversations, so you don't have to repeat yourself. - **[Apps](/user-guide/apps)** — features that extend an Agent or the platform. Apps can live inside a chat or run as full-page [standalone apps](/apps/standalone). ## Building on SAALT There are two tracks for extending the platform. ### Apps and Bridge Build an App to add views, Knowledge, and Tools that integrate directly into the SAALT UI. Apps talk to SAALT Core through the **Bridge** SDK. Start with the [App development guide](/apps/overview). ### APIs Interact with your Agents and core features programmatically: - **REST API** — the full SAALT contract for managing and calling Agents. See the [API overview](/api/overview) and the [API reference](/docs/core-api). - **[OpenAI-compatible API](/api/openai-compatible)** — a gateway that speaks the OpenAI chat-completions format, so existing OpenAI SDKs and tools can talk to your Agents with minimal changes. --- ## Document: Changelog Release notes for SAALT packages and APIs. URL: /changelog # Changelog Public release notes for SAALT packages. Versions follow [semver](https://semver.org). Entries use the [Keep a Changelog](https://keepachangelog.com) format. ## SAALT Core Release notes for the core SAALT platform — the web app, REST API, App runtime, and built-in features. These are the developer-facing technical notes; the in-app **What's New** covers the same releases at a higher, user-facing level. ### v1.3.0 — 2026-08-19 **Added** - **Skills** — a reusable, versioned procedure an Agent follows for a recurring task: the instructions plus the templates and files it needs. Backed by `skill`, `skill_version` and `skill_blob` tables with a `SkillStatus` lifecycle and per-provider references (`skill_version_provider_reference`); `skill_availability` rows scope which Agents may use a Skill. Uploaded resources are stored as blobs and handed to the Agent at execution time. - **Code execution** — Agents can run code over uploaded files and larger datasets in an isolated sandbox and return the result as a table, chart or report. Execution state is tracked per conversation in `conversation.codeExecutionState`. - **Container Apps** — an App can ship as a container image instead of only a proxied URL. `installed_plugins` gains a `type` discriminator (`PluginType`) plus `containerImage`, `containerPort` and `lastError`; registry credentials live in the new `container_registry_credentials` table. See [Apps](/user-guide/apps). - **Usage attribution and reset countdown** — `llm_invocation.isIncludedUsage` separates subscription-covered calls from billable ones, and the usage limit now shows when the current window resets. **Changed** - A new model selector in chat, and an updated analytics page. - The browser's native file API is used where available, with a fallback otherwise. - Cloning an Agent carries its full configuration, and per-Agent model overrides apply consistently. **Fixed** - **Cross-user conversation disclosure in the generate endpoints** — a supplied conversation id is now validated against the requesting user. Previously an id belonging to another user, or an arbitrary id against a public Agent, could read back that conversation's history. - Provider reasoning-item references are stripped when a turn is trimmed from history, so OpenAI-family models no longer fail on a truncated reasoning chain. - Token usage is recorded for streamed conversations. ### v1.2.4 — 2026-08-10 **Fixed** - Editing an App's URL now invalidates the cache across all replicas, instead of only the one that served the edit. - Provider-specific tool metadata is stripped from message history before it is replayed to a model. ### v1.2.3 — 2026-08-10 **Added** - **Time-based usage quotas** — per-user usage windows (`user.usageWindowStartedAt`) with weekly limits and reported overage. - Sampling controls (`temperature`, `top_p`, `top_k`) can be disabled per Agent. **Changed** - Personal Agents save a model change automatically. - Memory is not applied to API-driven streaming conversations; that path stays a raw model call. **Fixed** - `generateText` and `generateObject` in the core API reject `system`-role messages instead of accepting them silently. See the [API reference](/docs/core-api). - Spacing around rendered tool calls in chat. ### v1.2.2 — 2026-08-03 **Added** - Instances running without outbound connectivity fetch a default configuration. **Fixed** - An App's identifier is updated on refresh, so a renamed App keeps resolving. ### v1.2.1 — 2026-08-03 **Added** - **Mermaid diagrams** render in chat. See [Chat](/user-guide/chat). - **MCP re-authentication** — expired MCP OAuth grants are detected and surfaced, with error handling around the OAuth status check. - An App's URL can be edited after installation. See [Apps](/user-guide/apps). **Fixed** - Aborted response streams are handled without leaving the turn in a broken state. - Image-generation cost is reported correctly in usage analytics. ### v1.2.0 — 2026-07-03 **Added** - **Memory** — durable per-Agent, per-user facts carried across conversations, so answers stay relevant without re-explaining context each time. Personal (per-user) and shared (agent-wide) memories are stored separately and capped per bucket, gated by the Agent's `memoryEnabled` / `sharedMemoryEnabled` settings; a background pass consolidates and safety-screens entries before they are stored. See [Memory](/user-guide/memory). - **Standalone Apps** — Apps can run **agent-less** and full-page as their own destination, rather than always attached to an Agent's chat. Declared with `standalone: true` plus a stable `id` in the App's `/meta`, served under `/app/{id}`, and given a standalone-scoped Bridge (no `agentId` — agent-targeted Bridge calls are rejected). See [Standalone apps](/apps/standalone). - **OpenAI-compatible API** — a gateway at `/api/openai/v1` that speaks the OpenAI format, so existing OpenAI SDKs and tools work against SAALT with minimal changes. Implements `chat/completions` (streaming, tool calls, and token usage), `models`, and `embeddings`, plus a SAALT-specific `rerank` extension. Authenticate with an API key via `Authorization: Bearer` or `x-api-key`. See [OpenAI-compatible API](/api/openai-compatible). - **Personal agents** — user-owned Agents (`isPersonal`) scoped to a single user, alongside shared team Spaces; each user gets a personal chat Agent scaffolded automatically. - **Conversation API** — REST endpoints to list and continue conversations and read their messages: `GET` / `POST /llm/{agentId}/conversation` and `GET /llm/{agentId}/conversation/{conversationId}/messages`. Conversation search is backed by Postgres trigram and full-text indexes. See the [API reference](/docs/core-api). **Changed** - **Rewritten Chat experience** — an in-chat Agent switcher, drag-and-drop / paste file uploads, conversation search, per-Agent model preference, and deep-research progress indicators. Chat routing moved to `/space/{spaceId}/agent/{agentId}`; the legacy `/chat/{agentId}` path redirects. See [Chat](/user-guide/chat). :::note This is the first published SAALT Core release entry. Full historical core release notes will follow. For the current API contract, see the [API reference](/docs/core-api). ::: ## Bridge — [`@open-agent-kit/bridge`](https://www.npmjs.com/package/@open-agent-kit/bridge) Release notes for the TypeScript SDK that Apps use to talk to SAALT Core. See [Bridge SDK reference](/apps/bridge#bridge-sdk-reference) for the current method surface. ### [v1.2.0](https://www.npmjs.com/package/@open-agent-kit/bridge/v/1.2.0) — 2026-08-03 First stable 1.2.0 release — the standalone-bridge rework: bind-once scope, transcription, agent-less LLM generation, and conversation listing. Install with `npm i @open-agent-kit/bridge`. Changes below are relative to the previous stable release, v1.0.8. **Breaking** - `createBridge` now binds scope once at construction — `createBridge({ token, serverUrl, agentId? })`. Per-call `agentId` arguments were removed from `config`, `pluginData`, `knowledge`, `conversation`, `agent`, and `llm.getAvailableModels`; the scope is taken from the bridge instance instead. - Agent-only namespaces (`knowledge`, `conversation`, `agent`, `llm.getAvailableModels`) throw on a standalone (agent-less) bridge. **Added** - Standalone (agent-less) bridge support — omit `agentId` when constructing to talk to SAALT Core without an Agent scope. - Exported `BridgeScope` type: `{ type: "agent"; agentId } | { type: "standalone" }`. - `llm.transcribe(params)` — speech-to-text over an audio/video clip via Gemini. Params: `model`, `audioBase64`, `mimeType`, optional `prompt`; returns `{ text }`. Works on both standalone and agent-scoped bridges. - `llm.generateText`, `generateObject`, and `generateImage` now run on a standalone bridge (no agent required); the model resolves to the instance-wide default. - `conversation.findMany(filter)` — list an agent's conversations with an optional Prisma-style filter (`where` / `select` / `orderBy` / `skip` / `take` / `cursor`), returning a bare `Conversation[]`. Message bodies are not included (use `findFirst` per conversation, as they can be large); incognito and private conversations are never returned, and archived ones are excluded by default. Requires an agent-scoped bridge. See the [`bridge.conversation`](/apps/bridge#bridge-sdk-reference) reference. - `llm.getAvailableModels` now also returns `models: ModelInfo[]` alongside `defaultModel` and `availableModels` — the full metadata (`key`, `modelId`, `displayName`, `provider`, `region`, `tags`, `pricing`, …) for each allowed model, so a picker can show names and badges instead of raw keys. **Changed** - Forwarded proxy headers renamed: `oak_session_token` / `oak_server_url` → `saalt_session_token` / `saalt_server_url` (plus a new `saalt_base_path`). - The SDK's admin API base path moved to `/api/v1/bridge`. The legacy `/api/v1/admin` base path 307-redirects for backwards compatibility. ### [v1.0.8](https://www.npmjs.com/package/@open-agent-kit/bridge/v/1.0.8) — 2026-06-26 **Added** - `bridge.llm.generateSpeech(params)` — text-to-speech via Gemini TTS. Returns `{ base64, mimeType }` for the synthesized audio. Supports a single prebuilt `voiceName` or a multi-speaker `speakers` array (up to 2 speakers, each mapping a speaker label to a voice). **Breaking** - None. ### [v1.0.7](https://www.npmjs.com/package/@open-agent-kit/bridge/v/1.0.7) — 2026-06-25 **Added** - `bridge.llm.getAvailableModels(agentId)` — returns `{ defaultModel, availableModels }` for the Agent so Apps can gate model selection to what the Agent actually allows. **Changed** - `bridge.llm.generateImage` now accepts an optional `imageConfig` (`aspectRatio` — one of `1:1`, `4:3`, `3:4`, `16:9`, `9:16`; `resolution` — `1K`, `2K`, or `4K`) and an optional inpainting `mask` (a data URL). Existing calls without these fields are unchanged. **Breaking** - None. ### [v1.0.6](https://www.npmjs.com/package/@open-agent-kit/bridge/v/1.0.6) — 2026-05-26 **Added** - `bridge.agent.getAgent(agentId)` — returns `{ id, name, model }` for the given Agent so Apps can render the configured LLM model or display name. Token-scoped: the call returns `403` if the App's bearer token does not include the requested `agentId`. **Breaking** - None. ### [v1.0.5](https://www.npmjs.com/package/@open-agent-kit/bridge/v/1.0.5) — 2026-05-26 **Changed** - Raised the undici dispatcher `headersTimeout` from 30 minutes to 60 minutes so long-running LLM and file-parse calls don't time out at the SDK transport layer. **Breaking** - None. ### [v1.0.4](https://www.npmjs.com/package/@open-agent-kit/bridge/v/1.0.4) — 2026-05-08 **Added** - `bridge.knowledge.attachTagToDocument(agentId, documentId, tagId)` — attach an existing knowledge tag to a document. - `bridge.knowledge.removeTagFromDocument(agentId, documentId, tagId)` — detach a knowledge tag from a document. **Breaking** - None. ### [v1.0.3](https://www.npmjs.com/package/@open-agent-kit/bridge/v/1.0.3) — 2026-05-08 **Added** - `bridge.knowledge.createTag(agentId, tag)` — create a knowledge tag for an agent. - `bridge.knowledge.listTags(agentId)` — list knowledge tags for an agent. - `bridge.knowledge.updateTag(agentId, tagId, updates)` — update a knowledge tag's name or color. - `bridge.knowledge.deleteTag(agentId, tagId)` — delete a knowledge tag. - Types: `KnowledgeTag`, `KnowledgeTagInput`, `KnowledgeTagUpdate`. **Breaking** - None. ### [v1.0.2](https://www.npmjs.com/package/@open-agent-kit/bridge/v/1.0.2) — 2026-04-01 **Changed** - HTTP requests now use a custom [`undici`](https://undici.nodejs.org) `Agent` dispatcher with a 30-minute headers timeout, so long-running LLM calls aren't cut off by Node's default fetch timeout. **Added** - `undici ^7.24.6` runtime dependency. Node 18+ ships with undici, but Apps must allow it as a transitive dep. **Breaking** - None. ### [v1.0.1](https://www.npmjs.com/package/@open-agent-kit/bridge/v/1.0.1) — 2026-03-25 **Added** - Package `README.md` with installation snippet and a namespace reference table. No code or API changes in this release. **Breaking** - None. ### [v1.0.0](https://www.npmjs.com/package/@open-agent-kit/bridge/v/1.0.0) — 2026-02-09 Initial public release of `@open-agent-kit/bridge`. **Added** - `createBridge({ token, serverUrl })` factory and `OAKBridge` return type. - `decodeToken(token)` helper for inspecting the forwarded session token. - `Tools` class for registering an App's Tools, with exported `Tool` and `ToolExecuteParams` types. - Bridge namespaces: `bridge.data.config`, `bridge.data.pluginData`, `bridge.llm`, `bridge.files`, `bridge.knowledge`, `bridge.conversation`, `bridge.user`. - `PluginMeta` type and re-exported `FilePart` / `ImagePart` / `TextPart` from `@ai-sdk/provider-utils`. - Runtime dependencies: `zod`, `zod-from-json-schema`, `zodex`. **Breaking** - None (initial release). --- ## Document: Spaces How Spaces work in SAALT — creating one, switching between them, and managing the Agents inside. URL: /user-guide/spaces # Spaces A **Space** is a workspace that groups related Agents. Use Spaces to separate teams, projects, or customer tenants — each Space has its own Agents, Knowledge, Apps, prompt library, and permissions. **Where to find this:** Sidebar → **Spaces** ## Creating a Space From the **Spaces** page, click **Create Space**. In the dialog, fill in: - **Space Name** — a unique display name, e.g. *Marketing*. - **Space Slug** — a URL-safe identifier, e.g. `marketing`. Lowercase letters, numbers and hyphens only. - **Description** *(optional)* — what this Space is for. - **Space Color** — a colour to make the Space visually distinct in the sidebar. Click **Create** to save. The new Space appears in the list and in the sidebar. :::note Space creation may be restricted to specific roles. If the **Create Space** button is missing, your account doesn't have permission to create new Spaces. ::: ## Switching Spaces Click any Space in the sidebar, or click a Space card on the **Spaces** page. The sidebar will reload to show the Agents inside that Space and the recent chats scoped to it. ## Editing a Space Open the Space, then click the **Manage** button at the top right of the Space page. A **Space Settings** dialog opens with several sections in its own left sidebar: | Section | What it contains | |---|---| | **Settings** | The Space's name, color, and description, plus the **Danger Zone**. | | **Agent Templates** | Reusable Agent templates for this Space. *(Coming soon.)* | | **Agent Prompts** | The Space-level [Prompt Library](#agent-prompts-prompt-library) shared across all Agents in this Space. | | **Knowledge** | Knowledge that's available to every Agent in this Space (in addition to per-Agent Knowledge). | | **Users & Permissions** | Who can access this Space and what they can do. | Under **Settings**, the Space's fields sit directly under the title — you can change: - Space Name - Space Color - Description Changes are saved through a **floating save bar** (**Save Changes** / **Discard**) that appears once you edit a field. :::warning The **Danger Zone** at the bottom of **Settings** contains **Delete Space**. Deleting a Space also removes every Agent, Knowledge Document, and conversation inside it. This is permanent — there is no undo. ::: ## Agent Prompts (Prompt Library) The **Agent Prompts** section of Space Settings is the Space-level Prompt Library. From here you can click **Create New Prompt** to add reusable prompt templates that any Agent in the Space can use during a chat. Prompts created here are shared across the Space; per-Agent prompts can be managed inside each Agent's settings. ## What's inside a Space? Once you're in a Space you can: - Open any Agent and start chatting — see [Chat](/user-guide/chat). - Create or configure Agents — see [Agents](/user-guide/agents). - Manage shared Knowledge for the Space — see [Knowledge](/user-guide/knowledge). - Enable Apps on an Agent — see [Apps](/user-guide/apps). --- ## Document: Skills Teach an Agent a repeatable way of working — reusable instruction bundles with optional reference files, shared across a Space or scoped to a single Agent. URL: /user-guide/skills # Skills **Skills** are reusable instruction bundles an Agent can load on demand. Instead of re-explaining how you want a recurring task done, you write it down once as a Skill; the Agent loads it whenever the task comes up and follows it the same way every time. ## What a Skill is A Skill has three parts: | Field | What it does | | --- | --- | | **Name** | Identifies the Skill, e.g. `meeting-summarizer`. It must be unique wherever the Skill is available. | | **Description** | Tells the Agent *when* to use this Skill. This is what the Agent matches against, so describe the trigger, not the output — "Use when the user provides meeting notes or a transcript and wants them summarized." | | **Instructions** | The guidance the Agent follows *once* the Skill is loaded — the actual procedure, tone, and output format. | A Skill can also carry **reference files**: templates, examples, checklists, brand guidelines. The Agent does not read all of them up front. It loads the Skill's instructions, searches the bundled material for the part that is relevant, and reads only that — so a Skill can carry far more reference material than would fit in a single conversation. ## Where Skills live Skills are managed in two places, and where you add one decides who can use it: - **Space settings** — shared by every Agent in that Space. Agents show these as *inherited*, and they stay managed in the Space. - **Agent settings** — available to that one Agent only. Editing a Skill needs the matching permission for the Space or Agent it belongs to. A Space Skill and an Agent Skill cannot share the same name in the same place. ## Creating a Skill You have two ways in: **Write it inline.** Fill in name, description and instructions directly. You can add bundled files later by editing the Skill and uploading an archive. **Upload a bundle.** Either a `.md` file that carries the Skill's name and description as YAML frontmatter, or a `.zip` containing a `SKILL.md` alongside its reference files. Uploading is the route to take when the Skill already exists somewhere else, or when it is large enough that you would rather keep it in version control than in a text box. A minimal `SKILL.md`: ```markdown --- name: meeting-summarizer description: Use when the user provides meeting notes or a transcript and wants them summarized. --- Produce a structured summary that opens with a one-line overview, then lists key decisions, action items with owners, and open questions. ``` ## Versions Skills are versioned. Saving changes without publishing keeps them as a **draft**; publishing makes the new version the one Agents load. A published version is immutable — the next change becomes the next version rather than rewriting history. Edits to a Skill's instructions and details apply immediately once published. Replacing a Skill's files uploads a new `.md` or `.zip` over the existing bundle. Deleting a Skill removes it permanently. ## Turning Skills on An Agent uses Skills when they are available to it — through its Space or added directly. If an Agent should not use a Skill that its Space provides, manage that in the Space settings rather than removing the Skill. ## Skills and code execution When [code execution](/user-guide/code-execution) is enabled, a Skill's bundled files are placed into the Agent's sandbox as real files on disk. That is what makes Skills useful for work that is more than text: a Skill can ship a spreadsheet template or a script, and the Agent can run against it directly. Changing the Skills available to an Agent rebuilds its sandbox, so the next message in an existing conversation starts from a fresh environment. --- ## Document: Profile Managing your personal SAALT account — name, password, language preference, and account deletion. URL: /user-guide/profile # Profile Your personal account settings are separate from any Space or Agent — they apply to you across every Space you have access to. **Where to find this:** Click your avatar at the bottom of the sidebar → **My Account** The avatar menu also has **Report Issue** (report a problem with SAALT itself) and **Sign Out**. ## Personal Settings Under *Update your personal information* you can change: - **Name** — your display name. - **Email** — shown for reference; this is set by your administrator and cannot be edited here. - **Language** — interface language. Available values are **de** (German) and **en** (English). Edit the **Name** field and a **floating save bar** (**Save Changes** / **Discard**) appears — save from there to apply. ## Change Password Under *Update your account password*, enter: - **Current Password** - **New Password** - **Confirm New Password** Click **Update Password** to apply. :::tip Use a unique password that you don't use elsewhere. If your SAALT instance is configured with single sign-on, the password fields may be hidden — change your password through your identity provider instead. ::: ## Memory If your Agents remember things about you (see [Memory](/user-guide/memory)), the **Memory** section here lists those facts, grouped by Agent. From here you can: - Delete an individual memory. - **Clear agent memories** — remove everything one Agent remembers about you. - **Clear all memories** — remove every memory across all Agents. ## Danger Zone The **Danger Zone** at the bottom of the page contains **Delete Account**. :::warning *Permanently delete your account and all associated data.* Deleting your account is irreversible — your conversations, feedback, and any content you created will be removed. If you only want to step away temporarily, ask your administrator to disable your account instead. ::: --- ## Document: Memory How an Agent remembers durable facts about you across conversations — personal vs shared memories, when it's active, and how to manage or turn it off. URL: /user-guide/memory # Memory **Memory** lets an Agent remember durable facts and learnings about you across conversations *with that Agent* — so you don't have to repeat context every time you come back. ## What it is When Memory is on, an Agent can retain small, durable facts about you (preferences, ongoing projects, how you like answers formatted) and reuse them in later conversations with the same Agent. Memory is scoped per Agent — one Agent's memories are never shared with another. ## Personal vs shared memories Every memory is tagged either **only you** or **shared**: - **Personal ("only you")** — saved silently as you chat; visible only to you. - **Shared** — visible to *everyone* who uses that Agent. Because a shared memory affects other people, saving one requires an in-chat confirmation. A card appears asking *"Remember this for everyone using this assistant?"* with three choices: - **Confirm** — save it as a shared memory. - **Save for me only** — downgrade it to a personal memory. - **Discard** — don't save it. :::note If a proposed memory is flagged by the prompt-injection guard, it isn't stored and you'll see *"Memory not saved — flagged as unsafe."* ::: ## When it's active Memory is **off** for: - **Public Agents** (Agents that can be embedded externally), - **anonymous users**, and - **incognito** conversations (see [Chat](/user-guide/chat#the-chat-menu)). In your **Private Space**, every memory is personal — sharing is meaningless there, so the shared-memory confirmation doesn't appear. ## Turning it on or off Memory is controlled per Agent by the **Memory** toggle under **Agent Settings → Settings → Agent Capabilities** (see [Agents](/user-guide/agents#settings-tab--behaviour-toggles)). It's **on by default** and **unavailable for public Agents** (the toggle is disabled with the tooltip *"Memory is not available for public agents"*). Turning the toggle off later stops new memories from being saved but keeps any existing memories — they persist until deleted. ## Managing your memories You manage what Agents remember about you from the **Memory** section in **Personal Settings** (**My Account**) — see [Profile](/user-guide/profile#memory). Facts are grouped by Agent, and you can: - delete an individual memory, - **Clear agent memories** — remove everything one Agent remembers about you, or - **Clear all memories** — remove every memory across all Agents. ## Privacy & safety - Memories are **per-user** — one person's memories are never exposed to another (except deliberately shared ones, which apply to everyone using that Agent). - Every candidate memory is **screened before storage** and rejected if flagged unsafe. - Memories are **kept out of telemetry**. - Memory is **disabled** in incognito conversations and for public Agents and anonymous users. --- ## Document: Knowledge Managing Knowledge Documents for an Agent — uploading, tagging, monitoring embedding status, reviewing ingestion logs, and inspecting the RAPTOR section-summary index. URL: /user-guide/knowledge # Knowledge The **Knowledge Base** gives an Agent a pool of documents to search and cite. An Agent will only consult its Knowledge Base when **Knowledge Base** is enabled in [Agent settings](/user-guide/agents). **Where to find this:** open the Agent → kebab menu (top-right) → **Agent Settings** → **Knowledge**. ## Knowledge is layered Knowledge in SAALT lives at three levels, and an Agent searches all layers it has access to: | Level | Where to manage it | Visible to | |---|---|---| | **Agent** | Agent Settings → **Knowledge** *(this page)* | Just this Agent. | | **Space** | Space Settings → **Knowledge** (see [Spaces](/user-guide/spaces#editing-a-space)) | Every Agent in the Space. | | **Global** | Admin Tools → **Global Knowledge** (admin only) | Every Agent in the tenant. | This page documents the per-Agent view; the Space and Global views use the same tabs and controls — they just apply at a wider scope. ## The four tabs The Knowledge Base is split into four tabs: 1. **Documents** — uploaded files and synced Knowledge Documents. 2. **Index** — the RAPTOR section-summary index built from those Documents. See [Index tab](#index-tab) below. 3. **Logs** — ingestion and processing events. 4. **Settings** — chunking, embedding, and retrieval configuration. ## Documents tab Drop files onto the upload area: > *Drag and drop files here, or click to select* Each uploaded file becomes a Knowledge Document. The table shows: | Column | What it means | |---|---| | **Name** | File or document name | | **Last Modified** | When it last changed | | **Provider** | Where the document came from (local upload, SharePoint, Google Drive, etc.) | | **Scope** | Whether it's shared at the Space level or specific to this Agent | | **Status** | Processing state (see below) | | **Tags** | Knowledge tags applied to this document | A **Show inherited documents** toggle above the table reveals the Space- and Global-level Documents this Agent inherits, in addition to its own. ### Document status A document's status badge tells you whether it's ready to use: - **Pending** — queued, not yet processed - **Processing** — being parsed and embedded - **Ready** — fully embedded and searchable - **Failed** — something went wrong; check the **Logs** tab :::note A document only contributes to Agent answers once its status is **Ready**. If you've just uploaded something and the Agent can't find it, give it a minute and refresh. ::: ### Bulk actions Tick the checkbox at the start of each row to select documents, then use the bulk action bar: - **Download** — download the original files - **Add tag** — apply a Knowledge tag - **Remove tag** — remove a Knowledge tag - **Re-process** — run the documents through parsing and embedding again - **Force re-process** — re-process even when the content hasn't changed (bypasses the content-hash check) - **Delete** — permanently remove the documents ### Document detail view Click a document name to open its detail modal, which has three sub-tabs: - **Content** — preview the extracted text - **Chunks** — see how the document was split for retrieval - **Logs** — per-document ingestion events ## Index tab The **Index** tab shows the Agent's **Section Summaries** — a hierarchical RAPTOR index built from the leaf chunks of the Documents tab. The Agent consults these summaries during retrieval; pair them with the [**Include AI summary in results**](/user-guide/agents#settings-tab--behaviour-toggles) toggle to also show the matching cluster summary alongside grounded sections in chat. What you'll see on this tab: - **Build status** at the top: *"Building section summaries…"*, *"Section summaries up to date"*, or *"No section summaries built yet."* with the last-built timestamp. - **Build now** — clusters the current leaf chunks and produces summary nodes. **Full rebuild** discards the existing tree and re-clusters. **Cancel build** drops queued/delayed jobs (active jobs finish on their own). - **Scope** filter and a free-text filter for title / summary / id. - A table of summary nodes with columns *Title*, *Level*, *Summary*, *Leaves*, *Updated*, *Actions*. Click **Details** on a row to expand the full summary and the linked sections. ## Logs tab The **Logs** tab shows ingestion and processing events across all sources. If a document failed to embed, this is where to look for the reason. ## Settings tab The **Settings** tab is where Agent builders tune chunking, embedding behaviour, and the default Knowledge scope. Defaults are usually fine — only change these if you have a specific reason. :::tip Tags are the easiest way to scope retrieval to a topic. Apply consistent tags as you upload, and your Agent's answers become much more targeted. ::: --- ## Document: Getting started A quick tour of SAALT for new users and Agent builders — what Spaces, Agents, Knowledge, and Apps are, and where to find them in the app. URL: /user-guide/getting-started # Getting started This guide is for everyone who logs in to SAALT — whether you're here to chat with an Agent someone else built, or to build and configure your own. ## Signing in Open your SAALT instance and sign in with your work account. If you don't have one yet, ask your administrator to invite you — accounts cannot be self-registered on most installations. After signing in you land on the home dashboard. From here, the left sidebar is your main way to move around the app. :::note If your sidebar looks slightly different from what this guide describes, your administrator may have hidden some sections via permissions. Anything covered in this guide that you can't see is most likely a permission issue — contact your administrator. ::: ## The four things to know SAALT is organised around four concepts. Once you have a feel for these, the rest of the UI maps onto them. - **Space** — a workspace that groups related Agents. You can belong to one or several. See [Spaces](/user-guide/spaces). - **Agent** — a configured AI assistant inside a Space. It has its own model, system prompt, Knowledge, and Apps. See [Agents](/user-guide/agents). - **Knowledge Document** — files, wiki pages, or synced content an Agent can search and cite. See [Knowledge](/user-guide/knowledge). - **App** — an extension that gives an Agent extra capabilities or screens. See [Apps](/user-guide/apps). ## Where things live in the sidebar | If you want to… | Go to | |---|---| | See your dashboard | **Home** | | Search across your conversations | **Search** (opens a conversation-search modal) | | Pick a Space to work in | **Spaces** | | Work in your own personal space | **Private Space** | | Open a standalone App | **Apps** (shown only when you have at least one standalone App) | | Open an Agent and start chatting | A Space → select an Agent | | Update your profile or password | Your avatar at the bottom of the sidebar → **My Account** | The left sidebar also lists the Spaces you have access to under **Recent Spaces**, and your **Last Chats** below them. Click any chat to jump back into that conversation. :::note If you belong to only one Space and can't create new ones, the Spaces list is hidden and **Home** takes you straight into your single Space. ::: Admin-only areas live under **Admin Tools**, reached by clicking the tenant name at the top of the sidebar — for example **Global Settings**, **Apps**, **Users & Permissions**, **Global Knowledge**, **Variables**, **Insights & Analytics**, **LLM Calls**, **Activity**, and **Caches & Queues**. They are only visible if your role allows it. ## A typical first session 1. Open a Space from the sidebar. 2. Pick an Agent inside that Space. 3. Send a message in the chat. The Agent will reply, and if it uses Knowledge or Apps you'll see that surface in its response. 4. Leave a thumbs-up or thumbs-down if your administrator has enabled feedback capture. Ready to go deeper? Continue to [Spaces](/user-guide/spaces). --- ## Document: Embed a chat Place a SAALT Agent's chat on your own website — iframe, inline JavaScript, or floating chat bubble. Covers prerequisites, configuration, and customisation options. URL: /user-guide/embed # Embed a chat You can drop a SAALT Agent's chat onto any web page so visitors can talk to it without needing a SAALT account. Three methods are available, all generated for you from the Agent's **Embed** tab so you can copy-paste a ready snippet. **Where to find this:** open the Agent → kebab menu (top-right) → **Agent Settings** → **Settings** → **Embed** tab. ## Prerequisites Before any of the snippets will work, two things must be set on the Agent. ### 1. Public Agent Under **Agent Settings → Settings**, enable the **Public Agent** toggle. Without it the embed URL returns `403 "Agent is not public"`. See the toggle list in [Agents](/user-guide/agents#settings-tab--behaviour-toggles). ### 2. Allowed URLs In the same tab, fill in **Add Allowed URLs** with the domains you'll embed on. Separate multiple URLs with commas; `*` works as a wildcard. ``` https://example.com, https://example.com/*, https://*.example.com ``` The embed page sets `Content-Security-Policy: frame-ancestors 'self' ` — if the visitor's site isn't in the list, the browser silently blocks the iframe. :::warning The UI says "leave empty to allow all URLs" — that's misleading. An empty list means the iframe is only allowed from the SAALT instance itself. Always list every domain you'll embed on. ::: ## Configure the Embed tab The **Embed** tab has two parts: the **Embed Instructions** snippets (which you copy from) and an **Embed Settings** form (which controls how the embedded chat behaves). ### Embed Settings form | Field | What it does | |---|---| | **Maintain Conversation Session in Embed** | Number of minutes the conversation is kept in the browser's session storage. Non-zero lets a visitor reload the page and pick up where they left off. | | **Embed Window Title** | Header text shown inside the floating bubble. Doesn't apply to iframe or inline JS. | Click **Save Changes** when done. ### Snippet placeholders The snippets in the UI show your real Agent ID and the SAALT server URL filled in automatically — copy them from the app rather than typing by hand. In the examples below we use `` and `https://your-saalt-instance.example.com` as placeholders. ## Method A — iframe (simplest) The lowest-effort option. Drop this into any HTML page: ```html ``` **Choose this when** you just want chat inside a fixed-size container on a single page and don't need callbacks, custom theming, or to read messages from JavaScript. ## Method B — Inline JavaScript widget Renders the chat directly into a `
` you control — same DOM as your page, no iframe. ```html
``` **Choose this when** you want chat inline inside your own page layout, or you need to hook into messages (analytics, custom toasts) via the `onMessage` callback. The first argument to `renderChatComponent` is the target element's `id`. The second is the config object — see [Configuration options](#configuration-options). ## Method C — Floating chat bubble A small chat button bottom-right of the page that opens a chat window on click. Best for "help" use cases on a full site. ```html
``` **Choose this when** you want a chat affordance on every page of your site without committing layout space to it. The `floatingInitMessage` is the teaser text that appears in a popup bubble before the visitor opens the chat — a few seconds after the page loads. ## Configuration options Both the inline widget (`ChatComponent.renderChatComponent`) and the floating bubble (`SaaltChatWidget.renderChatWidget`) accept the same config object as their second argument. | Option | Required | Description | |---|---|---| | `agentId` | yes | The ID of your SAALT Agent. | | `apiUrl` | yes | The base URL of your SAALT deployment (e.g. `https://your-domain.com`). | | `meta` | no | An optional object passed to Agent tools for additional context. | | `avatarImageURL` | no | URL of an image to use as the assistant avatar. | | `initialMessage` | no | A message shown by the assistant before the visitor has typed anything. | | `onMessage` | no | A callback invoked with the full messages array after every reply. Useful for analytics. | | `floatingInitMessage` | no | *(Floating bubble only.)* Teaser text shown in the popup bubble before the visitor opens the chat. | The look and feel of the chat — colour scheme, intro text, suggested questions, language, file uploads — is controlled by the Agent's chat settings rather than the snippet config. See [Agents](/user-guide/agents) for those. ## Troubleshooting :::warning **The iframe is blank or the browser console shows a CSP `frame-ancestors` violation.** Your site's domain isn't in **Add Allowed URLs** on the Agent. Add it (with the protocol — `https://example.com`, not just `example.com`). ::: :::warning **`403 "Agent is not public"` appears in the iframe.** The **Public Agent** toggle is off. Enable it under Agent Settings → Settings. ::: :::note **Visitors lose the conversation when they reload the page.** Set **Maintain Conversation Session in Embed** to a non-zero number of minutes. The conversation is stored in the browser's session storage, scoped to that browser tab. ::: --- ## Document: Code execution Let an Agent run code over your files in an isolated sandbox and hand back the result as a table, chart, or report — how to enable it, what it can reach, and how long it lasts. URL: /user-guide/code-execution # Code execution **Code execution** lets an Agent run code to work with your data directly, instead of only describing what it would do. It inspects uploaded files, works through larger datasets, and returns the result as a finished file you can download from the chat. ## What it is With code execution enabled, the Agent can run shell commands inside an isolated container to inspect data, produce files, and refine its approach step by step. A typical turn looks like this: you send a message with a file, the Agent works on it in the sandbox, and any files it produces come back in the conversation as downloads. This is what makes an Agent useful for work that does not fit in a chat message — a spreadsheet with tens of thousands of rows, a folder of exports to reconcile, a chart that has to be generated rather than described. ## Turning it on Code execution is a per-Agent capability, switched on in the Agent's settings alongside file upload. Two things to know: - It **only works with models that support it.** If an Agent is set to a model without code execution, the capability stays inactive no matter the toggle. - It pairs with **file upload**. Without the ability to send files, there is little for the sandbox to work on. For personal Agents, code execution is governed centrally: an administrator enables it for everyone's personal Agent at once, rather than per user. ## The sandbox The Agent works in an isolated environment that is **reused across the messages of one conversation**. That is deliberate — it means intermediate results survive from one message to the next, so you can say "now group that by region" without re-uploading anything. The environment is rebuilt in two cases: - it **expires** after a period of inactivity, or - the **Skills available to the Agent change**, which requires a fresh environment. After a rebuild, files from earlier in the conversation are made available again, but anything the Agent created only inside the environment and never returned is gone. If a result matters, it is safest to have the Agent hand it back as a file. ## Files Files you upload and files the Agent produces are both kept with the conversation, so a download stays available after the sandbox itself is gone. Occasionally a produced file cannot be retrieved and the Agent will say so rather than pretend it succeeded — in that case, ask it to produce the file again. ## Working with Skills [Skills](/user-guide/skills) and code execution reinforce each other. A Skill's bundled files land in the sandbox as real files on disk, so a Skill can carry a template, a reference dataset, or a script that the Agent then runs against your input. The Skill supplies the *procedure*; code execution supplies the *ability to carry it out*. --- ## Document: Chat Talking to an Agent in SAALT — sending messages, conversation history, feedback, incognito mode, picking a model, and what happens when an App runs. URL: /user-guide/chat # Chat Chat is where you actually use an Agent. Open any Agent from a [Space](/user-guide/spaces) and you'll land in its chat view. **Where to find this:** Sidebar → *(your Space)* → *(an Agent)* Embedding chat on your own website? See [Embed a chat](/user-guide/embed) instead. The top of the chat shows the **Space** you're in followed by an **agent switcher** — click the Agent's name (and its chevron) to switch to another Agent or Space without leaving chat. The left sidebar lists **Last Chats in *(Agent name)*** so you can jump back into any earlier conversation; hover a chat for its **Rename** / **Delete** menu. ## Sending a message Type into the input at the bottom and submit. The Agent's reply streams in as it's generated. A chip below the input shows the **active model** (e.g. *Secure GPT120 ACP*). If the Agent allows multiple models, click the chip to switch — otherwise it just indicates which model will answer. **Attaching files** — drag files onto the chat input (you'll see *"Drop files to attach"*) or paste them directly, then send them along with your message. **Deep research** — if the Agent builder has enabled **Deep Research Mode**, a deep-research model selector appears in the chat input for running longer, multi-step research. ## Conversation history Past conversations appear in the sidebar under **Last Chats in *(Agent name)***. Click any item to reopen it and continue, or hover an item for its **Rename** / **Delete** menu. Use the **New Conversation** button at the top right to start a fresh thread. The sidebar **Search** item opens a modal to search across all your conversations. :::note Conversation history is only stored when **Conversation Tracking** is enabled in the [Agent settings](/user-guide/agents#settings-tab--behaviour-toggles). If you don't see history, your Agent builder has it turned off — your messages are still anonymously stored for privacy/compliance, just not surfaced here. ::: ## The chat menu The kebab (⋯) menu next to **New Conversation** has at least two entries: - **Incognito — Private Conversation** — toggle. When enabled: *"This is a private conversation. Your messages will not be saved."* Use it for sensitive prompts you don't want stored in conversation history. - **Agent Settings** — opens the [Agent admin dialog](/user-guide/agents). Only visible if you have permission to edit the Agent. If the Agent has any [Apps](/user-guide/apps) installed that ship a user-facing screen, they appear **to the left of the New Conversation button** — as inline items when there are two or fewer, otherwise grouped under an **Apps** dropdown. Click one to open that App's screen. ## Leaving feedback Each reply can carry a per-message toolbar with thumbs-up / thumbs-down buttons. Click one to record feedback — you'll see *"Thank you for your feedback!"* after submitting. Agent builders see this feedback under **Agent Settings → Feedback**. The thumb buttons only appear when **both** **Show Message Toolbar** and **Capture Feedback** are enabled by the Agent builder (see [Agent settings](/user-guide/agents)). ## When an App runs If the Agent has Apps enabled, you'll see them surface in chat in three ways: - **Mid-reply tool calls** — the Agent calls an App's tool and shows the result inline. - **Custom screens** — Apps that ship their own UI appear to the left of the **New Conversation** button, as inline items when there are two or fewer or grouped under an **Apps** dropdown otherwise. - **Prompt templates** — Apps can register prompt templates that appear alongside hand-authored prompts in the [Prompt Library](/user-guide/agents#prompts-system-prompt-vs-prompt-library). ## Remembering things about you Some Agents can **remember** durable facts about you across conversations. Personal memories ("only you") are saved silently. When an Agent proposes to save something visible to *everyone* using it, a confirmation card appears in chat asking *"Remember this for everyone using this assistant?"* with **Confirm**, **Save for me only** (keeps it personal), and **Discard**. See [Memory](/user-guide/memory) for the full picture. ## Prompt library shortcuts If your Agent or its Space has prompts in the [Prompt Library](/user-guide/agents#prompts-system-prompt-vs-prompt-library), you can pick one from the chat input to pre-fill your message with a tested template — useful for repeatable tasks like summarising a document or drafting a reply. --- ## Document: Apps Enabling and configuring Apps on an Agent — installed Apps, MCP servers, and how Apps surface in chat. URL: /user-guide/apps # Apps An **App** is an extension that gives an Agent extra capabilities — extra tools to call, custom screens, or connections to external systems. **Where to find this:** open the Agent → kebab menu (⋯, top-right) → **Agent Settings** → **Tools** ## Installed Apps The **Tools** screen lists every App available on this Agent as a card. Each card shows the App's name and a short description. If an App provides an admin configuration page, the card is clickable — opening it takes you to the App's own configuration UI. Apps are installed at the platform level by a system administrator. As an Agent builder you can enable them per Agent, but you cannot install new Apps yourself. :::note The set of Apps you see depends on what your administrator has installed and what your Space has access to. If an App you expected is missing, ask your administrator. ::: ## MCP servers Below the Apps list is the **MCPs** section. > *Add a Model Control Protocol (MCP) server to the agent to enable tool calling.* An MCP server gives the Agent access to a remote set of tools defined by the [Model Context Protocol](https://modelcontextprotocol.io/). Click **Add MCP** to register one. In the dialog, fill in: - **Name** — a label for this connection, e.g. *My Custom MCP*. - **Connection String** — the server URL, e.g. `https://example.com/mcp`. - **Additional Arguments (JSON)** — optional extra config (e.g. headers for authentication), tucked under an **Advanced options** disclosure. If the server requires OAuth, it shows an **OAuth** badge and exposes optional **Client ID**, **Client Secret**, and **Scopes** fields to complete the connection. Click **Save MCP** to register. ### Inspecting an MCP Each saved MCP appears as its own card, tagged **SSE** or **HTTP** depending on the connection type, with an **OAuth** badge when the server uses OAuth. Use the card actions to: - **View Tools** — see which tools this MCP exposes to the Agent, with name and description. - **Delete** (trash icon) — disconnect the MCP from this Agent. You'll be asked to confirm before removal. ## How Apps surface in chat When a user talks to an Agent that has Apps enabled, the Apps show up in three ways: 1. **Mid-reply tool calls** — the Agent calls an App's tool and shows the result inline. 2. **Custom screens** — Apps that ship their own UI appear as inline items, or grouped under an **Apps** dropdown, in the chat top bar (see [Chat](/user-guide/chat#when-an-app-runs)). 3. **Prompt Library entries** — Apps can register prompt templates, which appear alongside hand-authored prompts in the [Prompt Library](/user-guide/agents#prompts-system-prompt-vs-prompt-library). ## Standalone Apps Some Apps run **full-page** and aren't tied to a single Agent. When you've been granted access to one, it appears under the sidebar **Apps** item (top level) and opens on its own page at `/apps`. These behave like mini-applications inside SAALT rather than tools an Agent calls. ## Developer reference If you're building your own App rather than configuring an existing one, the developer documentation lives under [Apps overview](/apps/overview). --- ## Document: Agents Creating and configuring an Agent in SAALT — model selection, system prompt, chat parameters, knowledge, tools, feedback, and history. URL: /user-guide/agents # Agents An **Agent** is a configured AI assistant inside a [Space](/user-guide/spaces). It has its own model, system prompt, Knowledge access, and Apps. **Where to find this:** Sidebar → *(your Space)* → open an Agent's chat → kebab menu (top-right) → **Agent Settings** ## Creating an Agent Inside a Space, click **+ New Agent** at the top right of the Space page. You start by choosing how to set the Agent up: - **Custom Setup** — start from a blank Agent and configure everything yourself. - **Clone Existing Agent** — copy the configuration of an Agent you already have. - **Give agent knowledge** — begin by adding [Knowledge Documents](/user-guide/knowledge). - **Connect tools** — begin by wiring up [Apps](/user-guide/apps) or MCP servers. There's also an **Agent Inventor** ("Invent Your Agent") that drafts a starting configuration for you from a short description. Whichever route you take, give the new Agent a name and description so people in your Space know what it's for. ## The Agent admin dialog Opening **Agent Settings** brings up a dialog with the Agent's full configuration. The dialog has its own left sidebar with eight sections (plus an admin page for any installed App that ships one): | Section | What it contains | |---|---| | **Settings** | Four tabs: General, Settings, Embed, Danger Zone. See [below](#settings-section). | | **Chat Settings** | Chat-specific display and behaviour options for this Agent's chat view. | | **Prompt** | The Agent's system prompt — its persona, rules, and behaviour. | | **Knowledge** | Per-Agent [Knowledge Documents](/user-guide/knowledge) (Documents, Index, Logs, Settings tabs). | | **Tools** | Installed [Apps](/user-guide/apps) and MCP servers available to this Agent. | | **Feedback** | Review user feedback (thumbs-up / thumbs-down) on past responses. Requires **Capture Feedback** to be enabled. | | **History** | Browse and search past conversations with this Agent. Requires **Conversation Tracking**. | | **User & Permissions** | Who can access this Agent and what they can do. | ## Settings section The **Settings** section is itself split into four tabs. ### General tab - **Agent Name** — display name shown in the sidebar and to chat users. - **Description** — short summary of what this Agent does. - **Available Models** — pick which models this Agent can use. The default is used unless a user explicitly picks another. Each model row shows whether it's active, its tags (e.g. *Secure*, *OpenAI*), and a row action menu. Below the models, a **Writing Style** block holds two sliders that shape the Agent's tone: - **Temperature** — controls randomness. Lower values are more focused, higher values more creative. - **Top K** — limits word options. Lower values are more predictable, higher values offer more variety. A **Presets** block offers three one-click starting points for these sliders: | Preset | Temperature | Top K | |---|---|---| | **Formal** | 0.2 | 20 | | **Balanced** | 0.7 | 40 | | **Creative** | 1.0 | 64 | :::tip If you're not sure where to start, pick the **Balanced** preset (or leave Temperature and Top K at their defaults). Tune only after you've seen a few real conversations and have a reason to change. ::: ### Settings tab — behaviour toggles - **Active Agent** — if enabled, the Agent is available in chat. Turn off to hide it without deleting it. - **Public Agent** — if enabled, the Agent can be embedded in websites and external tools. Reveals **Add Allowed URLs**, where you list the sites permitted to embed it. Below these, **Agent Capabilities** groups the toggles that switch features on and off: - **Knowledge Base** — if enabled, the Agent can search its [Knowledge Documents](/user-guide/knowledge) to answer questions. When on, it reveals nested options: - **Knowledge Model** — the model used only for background section-summary (RAPTOR) work — not chat, and not embeddings. (The embedding model itself is now instance-wide, set by an administrator under **Admin Tools → Global Settings**.) - **Include AI summary in results** — when retrieving [Knowledge](/user-guide/knowledge), also include the matching RAPTOR cluster summary alongside the grounded source sections. Helps with broad thematic questions. - **Build section summaries automatically** — keep the RAPTOR [section-summary index](/user-guide/knowledge#index-tab) rebuilt as Documents change. - **Include section neighbours in results** — pull in the sections adjacent to a match for extra surrounding context. - **Capture Feedback** — enables the thumbs-up / thumbs-down controls on replies in chat. Required for the **Feedback** section to collect data. - **Memory** — lets the Agent remember durable facts about each user across conversations (default on). Disabled for public Agents (tooltip: *"Memory is not available for public agents"*). See [Memory](/user-guide/memory). - **Access the Web** — lets the Agent fetch live web content during a conversation. - **Conversation Tracking** — stores conversation history for later review. Required for the **History** section to be populated. - **Deep Research Mode** — enables longer, multi-step research in chat. When on, it reveals an **Available Deep Research Models** table for choosing which models the deep-research runs may use. ### Embed tab Configure how this Agent appears when embedded on an external website. The full walkthrough — including iframe, inline JavaScript, and floating bubble methods — lives in [Embed a chat](/user-guide/embed). Embedding only works when **Public Agent** is enabled and at least one entry exists in **Add Allowed URLs**. ### Danger Zone The Danger Zone tab has two sections. **Move Agent** relocates the Agent to a different [Space](/user-guide/spaces) — its configuration and Knowledge move with it. Below it, **Delete Agent** removes the Agent for good. :::warning **Danger Zone → Delete Agent** is permanent. Deleting an Agent removes its conversations, Knowledge associations, and App configurations. ::: ## Prompts: system prompt vs prompt library There are two related concepts for prompts on an Agent: - **System prompt** — the Agent's persona and rules. Edit it under **Agent Settings → Prompt**. - **Prompt Library** — reusable prompt templates a user can pick from in chat. There are two levels: Space-level (see [Spaces → Agent Prompts](/user-guide/spaces#agent-prompts-prompt-library), shared across every Agent in the Space) and Agent-level. Click **Create New Prompt** to add a template. ## Reviewing conversations and feedback If you've enabled **Conversation Tracking**, **Agent Settings → History** lists every conversation for the Agent. A toggle at the top lets you **show archived conversations**. If tracking is off, conversations are stored anonymously for privacy and won't appear here. If you've enabled **Capture Feedback**, **Agent Settings → Feedback** shows all thumbs-up / thumbs-down feedback users have left, useful for spotting good and bad responses over time. ## Saving Changes don't persist until you save them. As soon as you edit anything, a **floating save bar** appears with **Save Changes** and **Discard** — it stays until you commit or discard your edits. --- ## Document: Views Apps can render admin and user views inside SAALT — iframed React pages that talk back to the platform through the Bridge. URL: /apps/views # Views An **App** can expose two views that SAALT renders inside its own UI: an **admin view** for configuration and a **user view** for the end-user chat experience. Views are ordinary web pages your App serves; SAALT iframes them and forwards an auth token so they can call back through the [Bridge](/apps/bridge). ## How views fit into an App - SAALT learns which views you provide from [`/meta`](/apps/architecture): set `hasAdminChatPage` and/or `hasUserChatPage` to `true` to opt in — otherwise SAALT never requests those routes. - Each view is iframed inside SAALT and must respond with a renderable HTML page. - Every request carries a Bridge auth header, so the view can make authenticated calls back to SAALT. ```json // /meta — a user-only view { "name": "Transcription", "hasAdminChatPage": false, "hasUserChatPage": true } ``` ## Routing conventions - User view — served at `/app/{id}/user/:agentId`. - Admin view — served at `/app/{id}/admin/:agentId`. - `{id}` is your App's own `id`, as declared in [`/meta`](/apps/architecture) — SAALT forwards the full prefixed path to your App, so define your routes at that full path (e.g. with the `prefix()` helper in `routes.ts`). - The `agentId` route param identifies which Agent (and therefore which config/context) the page uses. :::note The `/app/{id}/user/:agentId` and `/app/{id}/admin/:agentId` conventions are for **agent-scoped** Apps. [Standalone Apps](/apps/standalone) are served **without** an `agentId`: SAALT renders the user surface at the top-level `/app/{id}/user` route, and the Bridge is created without an `agentId`. Note that `llm.generateText` still takes an `agentId` inside its params even so — see the [Bridge reference](/apps/bridge). ::: :::tip Migrating an existing App from unprefixed routes? See the [1.2 Migration guide](/apps/1-2-migration). ::: ## User view example A user view is a React Router route: a `loader` reads the `agentId`, and an `action` uses the Bridge (injected by your `bridgeMiddleware`) to do the work — here, a translation via `llm.generateText`. ```tsx import { data, type ActionFunctionArgs } from "react-router"; import { bridgeContext } from "~/context"; export const action = async ({ request, params, context }: ActionFunctionArgs) => { const bridge = context.get(bridgeContext)!; const agentId = params.agentId as string; const formData = await request.formData(); const text = formData.get("text") as string; const language = formData.get("language") as string; const translation = await bridge.llm.generateText({ prompt: `Translate to ${language}: ${text}`, options: { disableTools: true }, agentId, }); return data({ translation: translation.text }); }; ``` The UI is a React component that renders inside SAALT, submits via `
`, and reads results with `useActionData`. Because `bridgeMiddleware` injects the Bridge into the request context, the `action` can call any SAALT capability (LLM, Tools, config) on the Agent's behalf. ## Building your own view 1. **Flip the meta flags** in `app/routes/meta.ts` (`hasAdminChatPage` and/or `hasUserChatPage`). 2. **Add the route file** at `app/routes/admin/index.tsx` or `app/routes/user/index.tsx`. 3. **Wire the Bridge** — inject it via `bridgeMiddleware` on the parent layout, then read it from context in your `loader`/`action`. 4. **Handle data** with `loader` (read Agent-scoped data) and `action` (process form submissions). 5. **Render the UI** with your components; submit through React Router's `` so SAALT can hydrate state. 6. **Test inside SAALT** — see [Local development](/apps/local-development). ## Next steps - [Bridge](/apps/bridge) — the SDK your view calls. - [Tools](/apps/tools) — add a federated component to visualize a Tool result. - [Local development](/apps/local-development) — run and register your App locally. --- ## Document: Tools Give an Agent new abilities by defining Tools your App exposes — plus optional React components to render their results in chat. URL: /apps/tools # Tools **Tools** extend what an Agent can do: call external APIs, run custom logic, or fetch data on demand. When the LLM decides to use a Tool, SAALT calls your App to execute it. A Tool can also ship a UI component that visualizes its result in the chat. ## How Tools work SAALT calls your App's `/tools` route (GET) to list the available Tools; these are offered to the LLM. When the LLM invokes one, SAALT sends a POST to `/tools` with the chosen `toolIdentifier` in the body, and your App executes it. The [Bridge](/apps/bridge) helps you define and execute Tools, but you can implement the route in any language. ## Defining a Tool A Tool defined with the Bridge's `Tools` helper: ```typescript import { z } from "zod"; import { Tools, type ToolExecuteParams } from "@open-agent-kit/bridge"; type MyToolParams = { myMessage: string }; type MyToolResult = { message: string }; const tools = new Tools(); tools.registerTool({ identifier: "my-special-tool", name: "The clear name of my Tool", description: "A meaningful description that tells the LLM what this Tool does, and when and how to use it.", params: z.object({ myMessage: z.string(), }), execute: async ( params: ToolExecuteParams ): Promise<{ result: MyToolResult }> => { const { bridge, agentId, input } = params; const { myMessage } = input; // the arguments the LLM passed to the Tool // `bridge` is pre-authenticated — use it to reach SAALT (LLM, files, data, …). return { result: { message: `Handled: ${myMessage}` } }; }, }); export default tools; ``` `execute` must resolve to `{ result: unknown; error?: string }` — put your payload under `result`, and set `error` (a string) when the call fails. To implement Tools in another language, expose a `/tools` route that returns the array of Tool definitions (omit `execute`) and handle the POST yourself; the request body carries the `toolIdentifier` to dispatch on. ## Tool components SAALT can render a custom **federated React component** for a Tool's result in the chat UI. Add your federated component's name to the Tool definition, and the chat UI fetches it from your App. ## Next steps - [Bridge](/apps/bridge#using-tools-inside-apps) — the full `Tools` API and `ToolExecuteParams`. - [Views](/apps/views) — build the federated component that visualizes a Tool result. - [Architecture](/apps/architecture) — where `/tools` sits among an App's routes. --- ## Document: Standalone apps Full-page Apps that run outside any Agent or chat, rendered at a top-level /app/{id} route. URL: /apps/standalone # Standalone apps A **standalone App** is a full-page experience that is **not** tied to any Agent or chat. You declare one by returning `standalone: true` (and a stable `id`) from your App's [`/meta`](/apps/architecture) route. ## Where it renders SAALT Core renders a standalone App's user surface at the top-level URL `/app/{id}/user`, outside any Space, Agent, or chat context, proxying to your App's own `/app/{id}/user` route. An optional admin/config surface is embedded through the **Admin → Apps** "Configure" dialog — shown when your meta sets `hasAdminChatPage`. ## Bridge scope The token forwarded to a standalone App is **standalone-scoped**: it carries no Agent ids. Create the bridge **without** an `agentId`: ```ts import { createBridge } from "@open-agent-kit/bridge"; const bridge = createBridge({ token, serverUrl }); ``` Because there is no Agent in scope, the agent-only namespaces (`knowledge.*`, `conversation.findFirst`, `agent.getAgent`, `llm.getAvailableModels`) throw on a standalone bridge, while the scope-agnostic ones (`data.config`, `data.pluginData`, `files`) work — scoped **per-App**, not per-Agent. See [Agent-scoped vs standalone bridges](/apps/bridge#agent-scoped-vs-standalone-bridges) for the full comparison. ## Data Standalone App data is stored **per-App** (Agent-optional), so `pluginData` and `config` persist without an Agent. ## Access control Standalone Apps are **not** gated by per-Agent or per-Space availability. Access is granted per-App through permission groups (an "App Permissions" / "Use App" scope). A user sees the sidebar **Apps** entry and the `/apps` gallery only for the Apps they have been granted. ## Headers Requests to your App carry the same forwarded headers as agent-scoped views: - `saalt_session_token` - `saalt_server_url` - `saalt_base_path` --- ## Document: Apps overview Apps extend SAALT with views, Knowledge, and Tools — or run as full-page standalone apps. Start here, then follow the track. URL: /apps/overview # Apps overview An **App** is a small microservice that extends SAALT. SAALT registers it from its [`/meta`](/apps/architecture), proxies requests to it, and renders any UI it provides inside the platform. Apps are the primary way to add capabilities to an Agent — or to ship a full-page experience of your own. ## What an App can add - **[Views](/apps/views)** — admin and user pages rendered inside SAALT. - **[Knowledge](/apps/knowledge)** — documents fed into an Agent's Knowledge base. - **[Tools](/apps/tools)** — new abilities the LLM can call, with optional result components. - **[Standalone apps](/apps/standalone)** — an agent-less, top-level full-page surface at `/app/{id}`. Apps talk back to SAALT through the **[Bridge](/apps/bridge)** SDK (config, data, LLM, files, Knowledge, Tools). ## Reading order 1. [Architecture](/apps/architecture) — the microservice model and required routes. 2. [Views](/apps/views), [Knowledge](/apps/knowledge), [Tools](/apps/tools) — the capabilities you need. 3. [Bridge](/apps/bridge) — the SDK reference. 4. [Local development](/apps/local-development) — run and register your App. :::tip Clone the [App starter](https://github.com/open-agent-kit/plugin-starter-remix) to get running quickly. ::: --- ## Document: Local development Develop an App locally by exposing your dev server with a public URL and registering it in SAALT. URL: /apps/local-development # Local development You can develop an **App** locally and run it against a live SAALT instance — SAALT proxies to your machine over a public tunnel. ## Run and register your App 1. Install [ngrok](https://ngrok.com) (or a similar tunnel) to expose your local dev server on a public URL. 2. Start your App — e.g. `npm run dev` with the [App starter](https://github.com/open-agent-kit/plugin-starter-remix). 3. Start the tunnel and point it at your App's dev port. 4. In SAALT, go to **Admin → Apps**, click **Add App**, and enter your public tunnel URL. SAALT fetches `/meta` and registers the App. :::tip SAALT reaches your App over the public URL for every request (views, Tools, Knowledge), so the tunnel must stay running while you develop. When you change the capabilities in `/meta`, re-add or refresh the App so SAALT picks up the new flags. ::: ## Next steps - [Architecture](/apps/architecture) — the routes SAALT calls once your App is registered. - [Bridge](/apps/bridge) — the SDK your App uses to talk back to SAALT. --- ## Document: Knowledge Implement the two knowledge-provider routes so your App can feed documents into an Agent's Knowledge base. URL: /apps/knowledge # Knowledge An **App** can act as a **knowledge provider** — supplying documents that SAALT embeds into an Agent's Knowledge base. To enable this, declare `hasKnowledgeProvider: true` in [`/meta`](/apps/architecture) and implement two routes: `/knowledge/listDocuments` and `/knowledge/getDocument`. ## `/knowledge/listDocuments` Returns the list of documents the App wants to sync. SAALT compares this list against the previous response and enqueues new or changed documents for embedding. ```json [ { "id": "string", "name": "string", "lastUpdated": "string" } ] ``` - `id` — a stable identifier that uniquely identifies the document in your system. - `name` — the display name shown in the Knowledge base. - `lastUpdated` — timestamp of the document's last change; used to detect updates since the previous sync. ## `/knowledge/getDocument` Returns the full content of a single document. SAALT embeds the content and adds it to the vector store. ```json { "id": "string", "name": "string", "content": "string", "updatedAt": "string", "metadata": {} } ``` - `id` — the document identifier. - `name` — the document name. - `content` — plain-text content of the document. - `updatedAt` — timestamp of the last content update. - `metadata` — additional metadata as a JSON object. :::tip Use the Bridge's [`files.parseFile`](/apps/bridge#bridge-sdk-reference) helper to convert PDFs, Office files, and other formats to plain text before returning `content`. ::: ## Next steps - [Architecture](/apps/architecture) — where the `/knowledge/*` routes fit among an App's required routes. - [Bridge](/apps/bridge) — the SDK surface, including `files.parseFile`. - [Knowledge (user guide)](/user-guide/knowledge) — how end users see and manage documents. --- ## Document: Bridge The TypeScript SDK Apps use to talk to SAALT — read/write config and data, call the platform LLM, manage files and Knowledge, and register Tools. URL: /apps/bridge # Bridge The Bridge package is the thin client SAALT Apps use to talk to the host SAALT instance. It wraps the admin API with a tiny fetch helper so custom App views can read and write configuration, call the platform LLM endpoints, and manage App-owned data without rebuilding clients by hand. ## Installation ```bash npm install @open-agent-kit/bridge # or yarn add @open-agent-kit/bridge ``` ## Quick start ```ts import { createBridge } from "@open-agent-kit/bridge"; // In an App view route/handler SAALT forwards the auth token and server URL. const token = request.headers.get("saalt_session_token")!; const serverUrl = request.headers.get("saalt_server_url") ?? "https://oak.localhost"; // Standalone (agent-less) bridge — scope is per-App: const bridge = createBridge({ token, serverUrl }); // …or an agent-scoped bridge — pass the agentId once at construction: const agentBridge = createBridge({ token, serverUrl, agentId }); // Example: load persisted UI state const { config } = await bridge.data.config.get(); ``` Scope is bound once when you call `createBridge` and is fixed for the bridge's lifetime — provide `agentId` for an agent-scoped bridge, omit it for a standalone one. The bridge builds a pre-authorized fetch client with the provided `token` and `serverUrl` and then exposes namespaced helpers: - `data.config` – store small JSON configuration blobs per `agentId` (`get`/`set`). - `data.pluginData` – CRUD helpers for App data records (find, create, update, delete, count). *(SDK name retained for backwards compatibility.)* - `llm` – call the platform’s text, object, image, or conversation-generation endpoints. - `files` – read/write files in SAALT storage and parse uploaded content. - `knowledge` – list knowledge documents for an agent and enqueue syncs. - `conversation` – read conversation records: a single one with its messages (`findFirst`) or a filtered, paginated list (`findMany`). - `user` – retrieve information about the current user. - `agent` – read Agent metadata (id, name, configured LLM model). - `decodeToken` – inspect the forwarded session token when you need user metadata. Use these helpers directly inside your custom React routes, API handlers, or background jobs that need to coordinate with the SAALT backend. ## Bridge SDK reference The bridge is a TypeScript SDK — every helper below wraps an admin REST endpoint (see the [API category](/api/overview)) so App code can call it with normal types instead of hand-constructing HTTP requests. The signatures here are pulled directly from the published `@open-agent-kit/bridge` package; refer to this page rather than the REST spec when writing App code. ### Exported types ```ts import type { OAKBridge, // ReturnType BridgeScope, // { type: "agent"; agentId: string } | { type: "standalone" } Tool, // Tool ToolExecuteParams, // params passed to your tool's execute() PluginMeta, // shape used by host to describe an App (legacy type name) FilePart, // re-exported from @ai-sdk/provider-utils ImagePart, TextPart, ModelInfo, // per-model metadata returned by llm.getAvailableModels() } from "@open-agent-kit/bridge"; ``` `Tools` (class) is the only value export from the package — see the [tools section](#using-tools-inside-apps) below. `z` is **not** re-exported; import it from `zod` directly (`import { z } from "zod";`). ### `createBridge` ```ts createBridge({ token, serverUrl, agentId }: { token: string; serverUrl: string; agentId?: string }): OAKBridge; ``` Provide `agentId` for agent-scoped apps; omit it for a standalone bridge. Scope is bound once at construction and cannot change afterwards. ### Agent-scoped vs standalone bridges Passing `agentId` to `createBridge` yields an **agent-scoped** bridge; omitting it yields a **standalone** bridge. The chosen scope is fixed for the bridge's lifetime. - `config`, `pluginData`, and `files` work on **both** kinds of bridge. On a standalone bridge they are scoped per-App instead of per-Agent. - The agent-only namespaces — `knowledge.*`, `conversation.findFirst` / `conversation.findMany`, `agent.getAgent`, and `llm.getAvailableModels` — require an agent-scoped bridge. Calling any of them on a standalone bridge throws a client-side error: :::warning `This operation requires an agent-scoped bridge; create it with createBridge({ ..., agentId }).` ::: Scope is bound once at construction (rather than passed per call) since [`@open-agent-kit/bridge@1.2.0`](https://www.npmjs.com/package/@open-agent-kit/bridge/v/1.2.0). ### `bridge.data.config` Stores small JSON configuration blobs. Wraps `PUT /data/config` and `GET /data/config`; the scope is sent as an `?agentId` query param (omitted for a standalone bridge, where config is scoped per-App). ```ts bridge.data.config.set(config: JsonValue): Promise; // PUT /data/config?agentId=… bridge.data.config.get(): Promise<{ status: string; config: JsonValue }>; // GET /data/config?agentId=… ``` ### `bridge.data.pluginData` CRUD helpers for App-owned records. Records are keyed by the bridge's scope (Agent or App) + `identifier`. *(The namespace name `pluginData` is preserved from the original SDK API.)* ```ts bridge.data.pluginData.findUnique(identifier: string) : Promise<{ status: string; data: JsonValue }>; bridge.data.pluginData.findMany(filter?: PluginDataFilter) : Promise<{ status: string; data: JsonValue[] }>; bridge.data.pluginData.create(data: JsonValue, identifier: string) : Promise<{ status: string; data: JsonValue }>; bridge.data.pluginData.update(identifier: string, data: JsonValue) : Promise<{ status: string; data: JsonValue }>; bridge.data.pluginData.deleteOne(identifier: string) : Promise<{ status: string }>; bridge.data.pluginData.deleteMany(filter?: PluginDataDeleteFilter) : Promise<{ status: string; data: { count: number } }>; bridge.data.pluginData.count(filter?: PluginDataFilter) : Promise<{ status: string; count: number }>; ``` `PluginDataFilter` accepts `where`, `select`, `orderBy`, `skip`, `take`, `cursor`. `PluginDataDeleteFilter` accepts `where` only. ### `bridge.llm` ```ts bridge.llm.generateText(params: GenerateTextParams) : Promise<{ text: string }>; bridge.llm.generateObject(params: GenerateObjectParams) : Promise; // unwrapped; the SDK returns res.object bridge.llm.generateImage(params: GenerateImageParams) : Promise<{ base64: string }>; bridge.llm.getAvailableModels() : Promise<{ defaultModel: string; availableModels: string[]; models: ModelInfo[] }>; bridge.llm.generateSpeech(params: GenerateSpeechParams) : Promise<{ base64: string; mimeType: string }>; bridge.llm.generateConversationResponse(params: GenerateConversationResponseInputParams) : Promise<{ text: string }>; bridge.llm.transcribe(params: TranscribeParams) : Promise<{ text: string }>; ``` `generateText` and `generateObject` accept these params — both take an optional `agentId` (omitted on a standalone bridge, where usage is attributed to the App) and an optional `modelId` model override: ```ts type GenerateTextParams = { prompt?: string | Array; messages?: ModelMessage[]; // alternative to prompt (ModelMessage from the `ai` package) agentId?: string; // omitted by standalone bridges systemPrompt?: string; stream?: boolean; modelId?: string; // explicit model override (a config.models[] key) options?: { disableTools?: boolean; temperature?: number; topK?: number; }; }; type GenerateObjectParams = { schema: ZodType; // the object shape the model must return prompt?: string; messages?: ModelMessage[]; agentId?: string; // omitted by standalone bridges enumValues?: string[]; systemPrompt?: string; modelId?: string; }; ``` `transcribe` runs speech-to-text over an audio or video clip via Gemini (available since [`@open-agent-kit/bridge@1.2.0`](https://www.npmjs.com/package/@open-agent-kit/bridge/v/1.2.0)). It works on both standalone and agent-scoped bridges: ```ts type TranscribeParams = { agentId?: string; // omitted by standalone bridges model: string; // Gemini model id, e.g. "gemini-2.5-flash" audioBase64: string; // base64 audio/video bytes (data-URL prefix optional) mimeType: string; // e.g. "audio/mpeg", "video/mp4" prompt?: string; // optional extra instruction for the transcription }; ``` `generateSpeech` synthesizes audio from text via Gemini TTS (available since [`@open-agent-kit/bridge@1.0.8`](https://www.npmjs.com/package/@open-agent-kit/bridge/v/1.0.8)): ```ts type GenerateSpeechParams = { agentId?: string; // omitted by standalone bridges model: string; // Gemini TTS model id, e.g. "gemini-2.5-flash-preview-tts" text: string; // the final script to speak voiceName?: string; // single prebuilt voice, e.g. "Kore" speakers?: { speaker: string; voiceName: string }[]; // multi-speaker (max 2); takes precedence over voiceName }; ``` `GenerateImageParams` accepts an optional `imageConfig` and inpainting `mask` (available since [`@open-agent-kit/bridge@1.0.7`](https://www.npmjs.com/package/@open-agent-kit/bridge/v/1.0.7)): ```ts type GenerateImageParams = { prompt: string; agentId?: string; // omitted by standalone bridges provider: "openai" | "gemini"; model?: string; templateImages?: string[]; // base64 reference images imageConfig?: { aspectRatio?: "1:1" | "4:3" | "3:4" | "16:9" | "9:16"; resolution?: "1K" | "2K" | "4K"; }; mask?: string; // inpainting mask as a data URL }; ``` `generateConversationResponse` continues an existing conversation (or starts a new one) and returns the assistant's reply as text. Unlike the other `llm` methods, its `agentId` is **required** — this call always targets an agent: ```ts type GenerateConversationResponseInputParams = { agentId: string; // required — always targets an agent newMessage: UIMessage; // the incoming message (UIMessage from the `ai` package) conversationId?: string; // omit to start a new conversation customIdentifier?: string; // your own external id for the conversation numberOfMessagesToInclude?: number; // history window sent to the model }; ``` `getAvailableModels` reports the Agent's default model plus the full set of models it is allowed to use, so an App can present only valid choices. `availableModels` is the list of model keys; `models` carries the full `ModelInfo` for each (added in [`@open-agent-kit/bridge@1.2.0`](https://www.npmjs.com/package/@open-agent-kit/bridge/v/1.2.0)) — `key`, `modelId`, `displayName`, `provider`, `region`, `tags`, `description`, `pricing`, and similar metadata — so a picker can show names and badges rather than raw keys. It requires an **agent-scoped** bridge and throws on a standalone one. Available since [`@open-agent-kit/bridge@1.0.7`](https://www.npmjs.com/package/@open-agent-kit/bridge/v/1.0.7). ### `bridge.files` ```ts bridge.files.getFile(path: string) : Promise<{ status: string; base64: string }>; bridge.files.putFile(data: string, path: string, generatePublicUrl?: boolean) : Promise<{ status: string; path: string; publicUrl: string }>; // generatePublicUrl defaults to false bridge.files.parseFile(base64: string, path: string, config: ParseFileConfig) : Promise<{ status: string; parsedFile: { content: string; format: string } }>; ``` `data` and `base64` are base64-encoded file contents — the SDK does not handle raw binary. Files work on both standalone and agent-scoped bridges; on an agent-scoped bridge they are additionally namespaced per agent. `parseFile`'s `config` selects the extraction strategy: ```ts type ParseFileConfig = | { method?: "fast" } // deterministic text extraction (default) | { method: "model"; model?: { promptExtension?: string; maxOutputTokens?: number } }; // LLM-based parse ``` ### `bridge.knowledge` All `knowledge.*` helpers require an **agent-scoped** bridge and throw on a standalone one. ```ts // Documents bridge.knowledge.enqueueSync() : Promise<{ status: string }>; bridge.knowledge.listDocuments(filter?: KnowledgeDocumentFilter) : Promise<{ status: string; data: KnowledgeDocument[] }>; // Tags bridge.knowledge.createTag(tag: KnowledgeTagInput) : Promise<{ status: string; data: { id: string; name: string; color: string } }>; bridge.knowledge.listTags() : Promise<{ status: string; data: { tags: KnowledgeTag[] } }>; bridge.knowledge.updateTag(tagId: string, updates: KnowledgeTagUpdate) : Promise<{ status: string; data: { tag: { id: string; name: string; color: string; updatedAt: string } } }>; bridge.knowledge.deleteTag(tagId: string) : Promise<{ status: string }>; // Tag ↔ Document bridge.knowledge.attachTagToDocument(documentId: string, tagId: string) : Promise<{ status: string }>; bridge.knowledge.removeTagFromDocument(documentId: string, tagId: string) : Promise<{ status: string }>; ``` `KnowledgeDocument.status` is one of `"PENDING" | "PROCESSING" | "COMPLETED" | "FAILED"`. ### `bridge.conversation` ```ts bridge.conversation.findFirst(where: Record) : Promise; // one conversation, incl. its messages bridge.conversation.findMany(filter?: ConversationFilter) : Promise; // filtered list, without message bodies ``` Both require an **agent-scoped** bridge; they throw on a standalone one. - `findFirst` returns a single conversation matching `where`, **including its messages** (each reduced to `{ id, content }`) — use it to load one conversation's transcript. - `findMany` returns a **list** of the agent's conversations for a Prisma-style `filter`, as a bare `Conversation[]` under `data`. It does **not** include message bodies (fetch those per conversation with `findFirst` — they can be large). `incognito` and `private` conversations (the latter captured while conversation tracking was off) are never returned, and `archived` ones are excluded by default (pass `where: { archived: true }` to opt in). The scope is fixed to the bridge's agent, so an `agentId` in your `where` is ignored. `ConversationFilter` mirrors `PluginDataFilter` / `KnowledgeDocumentFilter` — it accepts `where`, `select`, `orderBy`, `skip`, `take`, `cursor`. A `Conversation` row (dates are ISO strings) is: ```ts type Conversation = { id: string; agentId: string; userId: string | null; tagline: string | null; customIdentifier: string | null; summary: string | null; archived: boolean; private: boolean; incognito: boolean; createdAt: string; updatedAt: string; }; type ConversationListResponse = { status: "success" | "error"; data?: Conversation[]; error?: string; }; ``` ### `bridge.user` ```ts bridge.user.me() : Promise<{ email: string; name: string; createdAt: string; updatedAt: string }>; ``` ### `bridge.agent` ```ts bridge.agent.getAgent() : Promise<{ id: string; name: string; model: string }>; ``` Returns the Agent record so an App can surface the configured LLM model or the Agent's display name (useful for "powered by …" UI chips). The `model` field is the model ID stored on the Agent (e.g. `gpt-4o`, `claude-3-5-sonnet`); it may be an empty string when the Agent inherits the platform default. The agent is fixed at construction, so `getAgent` requires an **agent-scoped** bridge and throws client-side on a standalone one. Available since [`@open-agent-kit/bridge@1.0.6`](https://www.npmjs.com/package/@open-agent-kit/bridge/v/1.0.6). ### `decodeToken` ```ts decodeToken(token: string): unknown; ``` Decodes the JWT payload via `atob` and returns the parsed object. Return shape isn't typed — treat it as the host's session claims. ## Using tools inside Apps The bridge exports a small Tools helper that lets you register executable tools with zod-validated params. Read more about tools in [the tools section](/apps/tools). ```ts import { Tools } from "@open-agent-kit/bridge"; import { z } from "zod"; const tools = new Tools(); await tools.registerTool({ identifier: "publish-note", name: "Publish note", description: "Save a note to App storage and attach it to the conversation", params: z.object({ content: z.string().min(1) }), async execute({ input, bridge, agentId, conversationId }) { const note = await bridge.data.pluginData.create(input, "note"); return { result: { note, conversationId } }; }, }); ``` The class exposes three methods: ```ts class Tools { registerTool(tool: Tool) : Promise>>; getTools() : Promise>; handleToolExecution( params: { toolIdentifier: string; agentId: string; input: unknown; messages: UIMessage[]; conversationId?: string; meta?: object; }, headers: Record, // forwarded saalt_session_token + saalt_server_url ): Promise<{ result: unknown; error?: string }>; } ``` - `registerTool` adds a tool definition with a zod schema for input validation. Throws if `identifier` is already registered. - `getTools` returns the tool list in JSON Schema form — used by the host for federated tool discovery. - `handleToolExecution` is invoked by the host: it validates `input` against the tool's params, builds a bridge from the forwarded headers, and runs your `execute` implementation. Apps normally don't call this themselves. It builds the bridge with the tool call's `agentId`, so `pluginData`, `config`, and `knowledge` calls inside a tool are automatically agent-scoped. Use this when your App exposes server-side tools that the agent can call while a custom view provides the front-end experience. ## Tips for custom views - Keep the `saalt_session_token` secret; the bridge already sets the `Authorization` header. - Prefer `pluginData` for user-generated or view-specific state and `config` for lightweight settings. - Decode the token with `decodeToken` if you need to inspect the current user inside your view logic. - Default `serverUrl` is `https://oak.localhost`; override it when the host provides a different origin. --- ## Document: Architecture An App is a small microservice that exposes a few required routes; SAALT proxies to it and renders its views inside the platform. URL: /apps/architecture # Architecture At its core, an **App** is a microservice that exposes a set of predefined routes. SAALT registers the App from its [`/meta`](#meta), proxies requests to it, and renders any views it declares inside the platform. ## `/meta` Returns the App's metadata — SAALT reads it to learn the App's name, version, and capabilities. ```json { "name": "Translator", "id": "translator", "icon": "https://example.com/translator.png", "version": "1.0.0", "description": "Translator App for SAALT", "author": "SAALT", "website": "https://saalt.ai", "standalone": false, "hasAdminChatPage": false, "hasUserChatPage": true, "hasKnowledgeProvider": false, "hasCron": false } ``` - `id` — optional URL-safe routing id (pattern `^[a-z0-9][a-z0-9._-]*$`). Used as the `/app/{id}` path prefix for all of your App's view routes; falls back to a slug of `name` if omitted. - `icon` — optional absolute `http(s)` or `data:` URI, shown on the Apps page. - `standalone` — optional boolean (default `false`); marks an agent-less, top-level App. See [Standalone apps](/apps/standalone). - `hasAdminChatPage` — set `true` to render a view in the Agent's admin section; requires an `/app/{id}/admin` route returning an HTML page. - `hasUserChatPage` — set `true` to render a view in the user's chat; requires an `/app/{id}/user` route returning an HTML page. - `hasKnowledgeProvider` — set `true` to supply Knowledge documents; requires the `/knowledge/*` routes. See [Knowledge](/apps/knowledge). - `hasCron` — optional boolean; when `true`, SAALT Core POSTs `/cron` to the App every 5 minutes with body `{ agentIds: string[] }`. Respond `200` immediately (30s timeout). ## Required routes Which routes your App must implement depends on the capabilities it declares in `/meta`: | Route | Required when | Purpose | | --- | --- | --- | | `/meta` | Always | Returns the App metadata (capabilities, name, version, …). | | `/app/{id}/user` (+ `/:agentId` when agent-scoped) | `hasUserChatPage` | Renders the user chat view. | | `/app/{id}/admin` (+ `/:agentId` when agent-scoped) | `hasAdminChatPage` | Renders the admin view. | | `/tools` | App defines Tools | Lists Tool definitions and handles execution. See [Tools](/apps/tools). | | `/knowledge/listDocuments` + `/knowledge/getDocument` | `hasKnowledgeProvider` | Provides Knowledge documents. See [Knowledge](/apps/knowledge). | | `/cron` | `hasCron` | Receives the scheduled cron POST described under `hasCron` above. | `/meta`, `/tools`, `/knowledge/*`, and `/cron` are **contract routes** — SAALT calls them directly, unprefixed. Your view routes (`/user`, `/admin`) must live under your App's own `/app/{id}/` prefix so SAALT can route to them without colliding with other installed Apps. For [standalone Apps](/apps/standalone), the user surface is served **without** an `agentId` — at `/app/{id}/user` rather than inside an Agent. :::note Still serving `/user`/`/admin` unprefixed? See the [1.2 Migration guide](/apps/1-2-migration). ::: ## Next steps - [Views](/apps/views) — build the admin and user views. - [Bridge](/apps/bridge) — talk to SAALT from your App with the SDK. - [Local development](/apps/local-development) — run and register your App locally. --- ## Document: 1.2 Migration guide Migrate an existing App to @open-agent-kit/bridge 1.2 — move your routes under the /app/{id}/ prefix so it can run alongside other installed Apps without collisions. URL: /apps/1-2-migration # 1.2 Migration guide `@open-agent-kit/bridge` 1.2 requires every App-specific route — user views, admin views, and any custom API routes — to live under a `/app/{id}/` prefix, so multiple Apps can run side by side without their routes colliding. A fixed set of contract routes that SAALT calls directly (`/meta`, `/tools`, `/knowledge/*`, `/cron`) stay unprefixed. This guide walks through migrating an existing App that still serves routes at the root (e.g. `/user`, `/api/progress`) to the prefixed convention. :::note Starting a new App from the [App starter](https://github.com/open-agent-kit/plugin-starter-remix)? It already ships with path-based routing — you can skip this guide. ::: ## Step 1: Update the Bridge package ```bash npm install @open-agent-kit/bridge@^1.2.0 npm install ``` This release also made `agentId` optional when constructing the Bridge for standalone Apps. If you're on an older Bridge version, see [Bridge](/apps/bridge#createbridge) for the (separate, optional) call-site changes. ## Step 2: Move your routes under the prefix In `app/routes.ts`, wrap your App's routes with its `id` (the same `id` your [`/meta`](/apps/architecture) route returns) using the `prefix` helper: ```ts // app/routes.ts import { type RouteConfig, route, prefix } from "@react-router/dev/routes"; export default [ // Unprefixed — SAALT calls these directly route("meta", "routes/meta.ts"), route("tools", "routes/tools.ts"), // Prefixed — agent-scoped App ...prefix("/app/my-app", [ route("user/:agentId", "routes/user/index.tsx"), route("api/:agentId/progress", "routes/api/progress.ts"), ]), // Prefixed — standalone App (no :agentId segment) ...prefix("/app/my-app", [ route("user", "routes/user/index.tsx"), route("api/progress", "routes/api/progress.ts"), ]), ] satisfies RouteConfig; ``` SAALT forwards the **full** `/app/my-app/…` path to your App — it does not strip the prefix, so your routes must be physically defined at that full path. ## Step 3: Fix every in-app reference This is the step that's easiest to do incompletely, and the bugs it leaves are subtle: `routes.ts` and obvious ``s get the prefix, but `useFetcher` action paths, plain `` tags, and **relative** paths get missed. A missed reference resolves against the current URL and 404s only when that one link or button is used — so it can pass a quick smoke test and still ship broken. Search for every reference and convert the ones that target your App's own routes: ```bash grep -rnE "navigate\(|redirect\(|` / `` | `/app/my-app/user/...` | | `useFetcher().submit(_, { action })` | `/app/my-app/api/...` | | `` / `` | `/app/my-app/api/...` | | `fetch("/api/...")` | `/app/my-app/api/...` | | plain `` | `/app/my-app/user/...` (see below) | Two rules apply to every row above: 1. **Use the full absolute path.** Every in-app reference must start with `/app/my-app/…`. 2. **Convert relative paths too — not just ones already starting with `/`.** There is no router basename (see Step 4), so a relative path is never auto-prefixed; it resolves against the current URL and breaks. A relative `to="user"` or `action: "api/progress"` is the more dangerous case because it looks deliberate. **Plain `` is a special case.** `` and `navigate` compute the path in JavaScript and ignore the document's ``. A plain `` does not — the browser resolves it against `` (Step 4) and triggers a full-page reload instead of client-side navigation. Prefer converting in-app plain anchors to `` with an absolute path. Keep a plain `` only for links that intentionally leave your App — the SAALT shell chat, `mailto:`, or external URLs. ## Step 4: Don't set a router basename — render `` instead A router `basename` would also prefix `/meta` and `/tools`, which must stay unprefixed. So instead: - Keep every route defined with the full `/app/my-app/…` prefix (Step 2). - Write every in-app navigation as an absolute `/app/my-app/…` path (Step 3). - Render `` once, in your root layout, using the `saalt_base_path` header SAALT forwards on every request (value `/app/my-app`). This is only for the browser to resolve plain `` and static assets — it does not affect ``/`navigate`, which are pure JavaScript. ```tsx // app/routes/user/index.tsx (layout) export const loader = ({ request }: LoaderFunctionArgs) => ({ basePath: request.headers.get("saalt_base_path") || "/", }); export default function Layout() { const { basePath } = useLoaderData(); return ( <> ); } ``` ## Step 5: Update your Vite config Set `base` to your App's prefix so built asset URLs resolve correctly under the proxy: ```ts // vite.config.ts export default defineConfig({ base: "/app/my-app/", // ... }); ``` ## Step 6: Rename legacy headers If your App still reads the pre-rename header names, update them: | Old | New | | ------------------- | --------------------- | | `oak_session_token` | `saalt_session_token` | | `oak_server_url` | `saalt_server_url` | | `oak_base_path` | `saalt_base_path` | Check middleware, route handlers, and any helper that extracts these values from the request. ## Next steps - [Architecture](/apps/architecture) — the routes SAALT calls once your App is registered. - [Bridge](/apps/bridge) — the SDK your App uses to talk back to SAALT. - [Local development](/apps/local-development) — run and register your App locally to test the migration. --- ## Document: Postman collection A ready-made Postman collection for the SAALT Core REST API, so you can start making calls quickly. URL: /api/postman # Postman collection A **Postman collection** for the SAALT Core REST API gets you making calls quickly. It covers the core REST API only — the [OpenAI-compatible API](/api/openai-compatible) is documented separately. Before you begin, set the `API key` and `AGENT_ID` variables in the collection settings. :::tip Download the Postman collection ::: ## Next steps - [API overview](/api/overview) — base URLs, auth, and the endpoint map. - [API reference](/docs/core-api) — the full OpenAPI reference. --- ## Document: API overview The SAALT REST API surface — base URLs, authentication, and a map of every endpoint group, linking to the full OpenAPI reference. URL: /api/overview # API overview SAALT exposes a **REST API** so external applications can manage and call Agents without building an App. This page maps the surface and links to the full reference; for request/response schemas see the [API reference](/docs/core-api). ## Base URLs - **Core API** — `/api/v1/core` (the SAALT contract for Agents, Spaces, LLM, Knowledge, cost control). - **[OpenAI-compatible API](/api/openai-compatible)** — `/api/openai/v1` (drop-in for OpenAI SDKs and tools). ## Authentication Create an API key in the SAALT dashboard under **Developer Settings**, granting it access to the Agents you need. Send it as: - `x-api-key: ` — for the core API. - `x-api-key: ` **or** `Authorization: Bearer ` — for the OpenAI-compatible API. ## Core API endpoints A grouped map — see the [API reference](/docs/core-api) for parameters, request bodies, and response shapes. **Agents** - `GET /agents` — list Agents. - `POST /agents` — create an Agent. - `PUT /agents/{agentId}` — update an Agent. - `PUT /agents/{agentId}/prompt` — update an Agent's system prompt. - `DELETE /agents/{agentId}` — delete an Agent. **Spaces** - `GET /spaces` — list Spaces. - `POST /spaces` — create a Space. - `PATCH /spaces/{spaceId}` — update a Space. - `DELETE /spaces/{spaceId}` — delete a Space. **LLM** - `POST /llm/{agentId}/generateText` — generate text. - `POST /llm/{agentId}/generateObject` — generate a structured object. - `POST /llm/{agentId}/generateImage` — generate an image. - `GET /llm/{agentId}/models` — list the Agent's allowed models. - `GET /llm/{agentId}/usage` — usage for the Agent. **Conversations** - `POST /llm/{agentId}/conversation` — continue or create a conversation and get the assistant reply. - `GET /llm/{agentId}/conversation` — list conversations. - `GET /llm/{agentId}/conversation/{conversationId}/messages` — get a conversation's messages. **Knowledge** - `GET` / `POST /knowledge/{agentId}/documents` — list / add documents. - `PUT` / `DELETE /knowledge/{agentId}/documents/{documentId}` — update / delete a document. - `GET` / `POST /knowledge/{agentId}/tags` — list / create tags. - `PUT` / `DELETE /knowledge/{agentId}/tags/{tagId}` — update / delete a tag. - `POST` / `DELETE /knowledge/{agentId}/documents/{documentId}/tags/{tagId}` — attach / detach a tag. **Cost control** - `GET` / `POST` / `PUT` / `DELETE /cost-control` — read and manage spend limits. ## OpenAI-compatible endpoints Base `/api/openai/v1` — see [OpenAI-compatible API](/api/openai-compatible) for details. - `POST /chat/completions` — chat completions (streaming, tool calls, usage). - `GET /models` — list available models. - `POST /embeddings` — create embeddings. - `POST /rerank` — rerank documents against a query (SAALT extension). ## Next steps - [API reference](/docs/core-api) — the full OpenAPI schemas. - [Postman collection](/api/postman) — try the API quickly. - [OpenAI-compatible API](/api/openai-compatible) — reuse existing OpenAI SDKs and tooling. --- ## Document: OpenAI-compatible API Call SAALT's configured models through an OpenAI-compatible endpoint with any OpenAI SDK. URL: /api/openai-compatible # OpenAI-compatible API SAALT exposes an OpenAI-compatible API so you can reuse existing OpenAI SDKs and tooling. Point any OpenAI client at the SAALT base URL, authenticate with your SAALT API key, and call `chat/completions`, `embeddings`, and `models` as you would against OpenAI. The gateway adds one endpoint OpenAI does not have, `rerank`, under the same base URL and authentication. ## Base URL ``` https:///api/openai/v1 ``` Set this as the `baseURL` of your OpenAI client. This is separate from the core REST API (which lives under `/api/v1/core`, documented in the [API Reference](/docs/core-api)). ## Authentication Authenticate with your SAALT API key using **either** header: - `Authorization: Bearer ` - `x-api-key: ` Create and scope keys in the SAALT dashboard under **Developer Settings**, the same as for the core API. Errors are returned in the OpenAI error envelope: ```json { "error": { "message": "...", "type": "...", "param": null, "code": "..." } } ``` ## Raw model gateway, not agent chat :::warning This API is a **raw model gateway**, not an Agent conversation endpoint. No Agent system prompt, knowledge, tools, or memory are applied to your requests — the input is sent straight to the model. If you need Agent behavior (retrieval, tools, conversation state), use the core [conversation endpoint](/docs/core-api) instead. ::: The `model` field must be the id of a model configured in your SAALT instance. Retrieve the available ids from the `GET /models` endpoint below. An unknown model id returns `404` with code `model_not_found`. ## POST /chat/completions Create a chat completion. ### Request | Field | Type | Notes | | --- | --- | --- | | `model` | string | **Required.** A configured model id (see `GET /models`). | | `messages` | array | **Required.** OpenAI-style chat messages. | | `temperature` | number | Optional. | | `top_p` | number | Optional. | | `max_completion_tokens` | number | Optional. Takes precedence over `max_tokens` when both are sent. | | `max_tokens` | number | Optional. Legacy alias for `max_completion_tokens`. | | `stop` | string \| string[] | Optional. | | `seed` | number | Optional. | | `frequency_penalty` | number | Optional. | | `presence_penalty` | number | Optional. | | `tools` | array | Optional. OpenAI tool definitions. | | `tool_choice` | string \| object | Optional. | | `stream` | boolean | Optional. Stream the response as Server-Sent Events. | | `stream_options.include_usage` | boolean | Optional. Include a final usage chunk when streaming. | **Not supported:** `n`, `logprobs`, `response_format`, `logit_bias`. ### Response For a non-streaming request, the response is a standard `chat.completion` object with `choices[].message` and a `usage` object. When `stream` is `true`, the response is a Server-Sent Events stream of `chat.completion.chunk` objects (each carrying a `choices[].delta`), terminated by a final `data: [DONE]` line. ### Errors - `404` (`model_not_found`) — the requested `model` is not configured. - `429` (`type: "rate_limit_error"`, `code: "usage_limit_exceeded"`) — the API key's usage limit has been exceeded. ### Example — curl ```bash curl https:///api/openai/v1/chat/completions \ -H "Authorization: Bearer $SAALT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "", "messages": [ { "role": "user", "content": "Hello!" } ] }' ``` ### Example — OpenAI Node SDK ```ts import OpenAI from "openai"; const client = new OpenAI({ baseURL: "https:///api/openai/v1", apiKey: process.env.SAALT_API_KEY, }); const completion = await client.chat.completions.create({ model: "", messages: [{ role: "user", content: "Hello!" }], }); console.log(completion.choices[0].message.content); ``` ## POST /embeddings Create embeddings for one or more inputs. ### Request | Field | Type | Notes | | --- | --- | --- | | `model` | string | **Required.** Must be a configured embedding model id. | | `input` | string \| string[] | **Required.** Up to 2048 inputs. | | `encoding_format` | `"float"` \| `"base64"` | Optional. | | `dimensions` | number | Optional. Truncates each returned vector to this length. | ### Response ```json { "object": "list", "data": [ { "object": "embedding", "index": 0, "embedding": [/* ... */] } ], "model": "", "usage": { "prompt_tokens": 0, "total_tokens": 0 } } ``` ### Errors - `404` — the requested `model` is not configured. - `400` — more than 2048 inputs were supplied. ### Example — curl ```bash curl https:///api/openai/v1/embeddings \ -H "Authorization: Bearer $SAALT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "", "input": "The quick brown fox" }' ``` ## GET /models List the configured chat and embedding models. ### Response ```json { "object": "list", "data": [ { "id": "", "object": "model", "created": 0, "owned_by": "" } ] } ``` ### Example — curl ```bash curl https:///api/openai/v1/models \ -H "Authorization: Bearer $SAALT_API_KEY" ``` ## POST /rerank Score a set of documents against a query and return them ordered by relevance. This endpoint has no OpenAI counterpart — it follows the same request and error conventions as the rest of the gateway so the same client and key work for it. ### Request | Field | Type | Notes | | --- | --- | --- | | `model` | string | **Required.** The reranker model configured for your instance. It is **not** returned by `GET /models`, which lists only chat and embedding models — ask your SAALT administrator for the id. Any other value returns `404` with code `model_not_found`. | | `query` | string | **Required.** The text the documents are scored against. | | `input` | string \| string[] | **Required.** The documents to rank. A single string is treated as a one-element list. | | `top_k` | number | Optional. How many of the highest-scoring documents to return. Defaults to all supplied documents. | ### Response ```json { "object": "list", "data": [ { "object": "document", "document": { "text": "The quick brown fox" }, "index": 0, "relevance_score": 0.98 } ], "model": "", "usage": { "total_tokens": 0 } } ``` `index` refers to the document's position in the request's `input` array, so you can map a result back to your own list. Entries are ordered by `relevance_score`, highest first. ### Errors - `400` (`invalid_body`) — `model`, `query` or `input` is missing. - `400` (`invalid_input`) — the document list is empty. - `404` (`model_not_found`) — the requested `model` is not the configured reranker. - `429` (`type: "rate_limit_error"`, `code: "usage_limit_exceeded"`) — the API key's usage limit has been exceeded. ### Example — curl ```bash curl https:///api/openai/v1/rerank \ -H "Authorization: Bearer $SAALT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "", "query": "How do I reset my password?", "input": [ "Password resets are handled in Profile settings.", "Our office is open Monday to Friday." ], "top_k": 1 }' ``` :::note This gateway is documented here in prose only. The canonical REST reference at [/docs/core-api](/docs/core-api) covers the core API and does **not** include the OpenAI-compatible endpoints. :::