# Configs & profiles Source: https://docs.omnara.com/agents/configuration Define agents with YAML or JSON An agent config is a YAML or JSON document that defines the agent's instruction, model, tools, permissions, and machines. Configs are immutable: changing the source creates a new config ID (`acfg_…`). Submitting the same source again returns the same ID. YAML and JSON configs refer to model providers, configured models, BYO machines, and machine pools by their human-readable names. When a config is created, Omnara resolves those names and stores the resources' immutable IDs in the compiled config. Renaming a resource therefore does not change an existing config or an agent already using it. Recompiling saved source resolves its names again against the current resources, so an old name may stop resolving or may resolve to a different resource if that name has since been reused. Provider names are unique within an organization, model names within a provider, and BYO machine and machine-pool names within an organization. Machine and pool resolution also requires the project to have access to the selected resource. The example below includes every major section. If you are creating your first agent, start with the smaller config in the [Quickstart](/quickstart). ```yaml theme={null} version: v1 instruction: | Help engineers investigate issues in the acme/api repository. Run read-only commands freely; ask before anything destructive. model: provider_config: openai-prod name: gpt-6-sol reasoning: effort: high tools: web_search: {} ask_question: {} run_command: permission: mode: always_ask write_process: {} read_process: {} stop_process: {} watch_process: {} list_processes: {} list_machines: {} inspect_machine: {} create_ticket: type: custom description: File a ticket in our internal tracker. input_schema: type: object properties: title: { type: string } body: { type: string } required: [title] mcp: linear: url: https://mcp.linear.app/mcp auth: type: oauth secret_id: sec_gik7mv4qtrwz3jehcyd5n6a2bf machine_sources: - machine_pool_name: default-pool cwd: /workspace skills: - skl_ik7mv4qtrwz3jehcyd5n6a2bfg subagents: researcher: type: profile profile: research-agent description: Read-only codebase investigation. max_instances: 4 fork: type: self instruction: append: You are a subagent. Finish with a concise summary. max_subagents: 8 max_depth: 2 ``` ## Instruction and identity | Field | Required | Notes | | - | - | - | | `version` | no | Only `v1` today; include it for forward compatibility. | | `instruction` | **yes** | The system-level brief. This is the single highest-leverage field you control — be specific about the job, the boundaries, and when to ask a human. | ## Model `model` selects a model configured in Omnara. `provider_config` is the name of a [model provider](/organization/model-providers), and `name` must exactly match one of its configured models. The model must also be available to the project. ```yaml theme={null} model: provider_config: openai-prod name: gpt-6-sol cache_retention: short reasoning: effort: high ``` Most agents can use the configured model's defaults. Override these fields only when needed: | Field | What it does | | - | - | | `context_window_tokens` | Budget for input and output tokens. Triggers compaction when estimated input exceeds the remaining budget after output and safety reserves. The turn fails if compaction cannot make it fit. | | `default_max_output_tokens` | Per-request output-token cap sent to the provider. | | `cache_retention` | Prompt-cache preference: `none`, `short` (default), or `long`. | | `reasoning.effort` | Reasoning effort for models that support it (for example `low`, `medium`, `high`). | ## Tools Add a tool under `tools:` to make it available to the agent. Use `{}` to accept its defaults. You can add **built-in tools** that Omnara runs or **custom tools** that your own system runs. MCP tools are configured separately. ### Built-in tools Enable a built-in tool by name. The [Built-in tools](/tools/built-in) page explains the available tools and their inputs. You can also change a tool's default permission: ```yaml theme={null} tools: web_search: {} # enabled, default permission run_command: permission: mode: always_ask # a human approves every command delete_machine: enabled: false # explicitly off ``` Permission modes decide whether a tool runs immediately, asks a person first, or is denied. See [Tools & permissions](/tools/permissions) for defaults and supported modes. When `machine_sources` is nonempty, Omnara adds missing entries for `run_command`, `write_process`, `read_process`, `stop_process`, `watch_process`, `list_processes`, `list_machines`, `inspect_machine`, `storage_upload`, and `storage_download` to the compiled config. If any source is a machine pool, it also adds `create_machine` and `delete_machine`, regardless of pool counts or machine availability. Attached skills similarly add `skill`, and [`git_credentials`](/integrations/github#git-credentials) adds `list_github_repositories`. Compilation leaves your YAML or JSON source unchanged. Explicit disables and permissions take precedence. The builder displays these defaults under **Tools → Other tools** without writing them into your source. Changing a permission or selecting **Disabled** saves an explicit override. Defaults disappear when their relevant source is removed; explicitly configured tools remain. Existing machine configs gain missing machine-tool defaults only when saved again. The builder previews that saved result, not the tools currently stored for an existing config. ### Integration capabilities Create a project integration first, then reference its tools as `tools.int____`. Tool entries contain enabled state, deferred loading and permissions; handler entries use `interaction_handlers: {integration-name: {}}`. The shipped tools use immutable integration-agent context established by a provider or scheduled launch. They fail before provider I/O if no conversation is assigned. Their interaction handlers use the same assigned conversation and accept empty selection arguments. Incoming subscriptions belong to the integration and are attached through launch requests or the integration subscriptions API, not config. Removing a tool or changing config does not remove subscriptions. Integration names resolve to immutable IDs when compiled, while credentials remain live on the integration. See [Integrations](/integrations/overview) for examples and provider launchers. Integration tools, subscriptions and interaction handlers are not inherited by spawned subagents. ### Git credentials Use a connected GitHub integration for HTTPS Git authentication in the agent's machine processes: ```yaml theme={null} git_credentials: integration: reviews repository_ids: [123456789] # optional read_only: true # optional ``` Compilation pins the integration's immutable ID. No assigned PR, integration tool or subscription is required. Credentials inherit the connected installation's granted repositories, or only those in `repository_ids`, and are read-only unless the integration allows pushing code and `read_only` isn't set; they are not restricted to an assigned PR. Manage those grants in GitHub. See [GitHub credentials](/integrations/github#git-credentials) for setup and daemon requirements. GitHub launchers add this capability automatically. An explicit profile choice wins as a whole, even when it references a different GitHub integration. Spawned subagents do not receive Git credentials, including those configured in their selected profile. ### Deferred tools Set `deferred: true` on any tool (built-in, custom, or MCP) to keep its definition out of the model's context until the model asks for it. Deferring a tool implicitly enables the built-in `tool_search` tool, which the model calls with a regular expression to load matching deferred tools by name, description, and argument names. Keep the tools the agent needs on almost every turn loaded; defer large or rarely used tool sets. ```yaml theme={null} tools: web_search: {} # always loaded web_fetch: deferred: true # loaded only after a tool_search match ``` Deferred loading keeps the provider's prompt cache intact on Anthropic Messages and OpenAI Responses. On Chat Completions the model calls loaded tools through `call_deferred_tool` with the tool's name and arguments, so `call_deferred_tool` is a reserved tool name. ### Custom tools A custom tool describes an operation that your system performs. Its `description` tells the model when to use it, and its `input_schema` defines the arguments. The full example above defines `create_ticket` as a custom tool. When the agent calls it, Omnara waits for your system to submit the result. [Custom tools](/tools/custom) walks through that workflow. ## MCP servers `mcp` connects the agent to [Model Context Protocol](https://modelcontextprotocol.io) servers. Each entry sets the server URL, authentication, and permissions. `auth.secret_id` points to the [secret](/organization/secrets) required by the selected authentication type. [MCP servers](/tools/mcp) explains the full setup. ## Event webhooks [Event webhooks](/events/webhooks) are managed separately from agent configuration. Attach them to an agent, profile, or project to receive selected events at an HTTPS endpoint. ## Machines For new agents only, the builder preselects a cluster-managed pool granted to the project if one exists; otherwise, it starts without a machine source. `machine_sources` provides execution environments for tools such as `run_command`. Use a machine you connected yourself or let Omnara create one from a pool: ```yaml theme={null} machine_sources: - machine_name: build-server-1 # a specific BYO machine… cwd: /srv/repo env_overlay: CI: "true" secret_env_overlay: GITHUB_TOKEN: sec_7jehcyd5n6a2bfgik7mv4qtrwz - machine_pool_name: default-pool # …or capacity from a pool machine_memory_mb: 4096 max_machines: 3 ``` `env_overlay` sets environment variables. `secret_env_overlay` references [secrets](/organization/secrets), whose values are resolved when they are injected. Both apply to every process the agent runs on the machine; for pool sources they also apply to the machine environment at provisioning, so the pool's startup script sees them. Pool sources can also set machine size and limits; supported fields vary by provider. See [Machine pools](/machines/pools#create-a-pool) and the [config schema](/api-reference/endpoints/configs-and-profiles/create-agent-config). If a referenced secret is deleted or its project grant is revoked, subsequent processes and pool provisioning omit that environment variable. Environment names beginning with `OMNARA_` are reserved case-insensitively and cannot be set through agent or machine configuration. Agent processes receive `OMNARA_HOME` from the daemon as the location for Omnara-managed files. ## Skills `skills` attaches reusable instructions and files that the agent can load with the `skill` tool. The project must own or hold a [grant](/tools/skills#grant-it-to-projects) for each referenced skill. ## Subagents `subagents` declares the helpers an agent may spawn, keyed by a name the agent passes to the `spawn_agent` tool. Declaring at least one key adds missing entries for the five subagent tools (`spawn_agent`, `read_agent`, `send_agent_message`, `stop_agent`, `list_agents`) to the compiled config without changing source; existing entries keep their enablement and permissions. `spawn_agent` requires at least one key, but the other four can be listed under `tools` without any keys, so an agent whose config dropped its `subagents` can still read, message, and stop the subagents it already has. ```yaml theme={null} subagents: researcher: type: profile profile: research-agent description: Read-only codebase investigation. model: provider_config: openai-prod name: gpt-6-luna instruction: append: Report findings as a bullet list. max_instances: 4 archive_after_idle_minutes: 60 fork: type: self model: reasoning: effort: medium max_subagents: 8 max_depth: 2 ``` | Field | What it does | | - | - | | `type` | `profile` launches from the named profile's current config. `self` launches from a copy of this agent's own config. | | `profile` | Profile name; required for `type: profile`, rejected for `type: self`. | | `description` | Shown to the model when it chooses a subagent key. | | `model` | Set both `provider_config` and `name` to select a model at compilation. Omit both to inherit the parent's model for `self` or the profile's current model for `profile`. Reasoning and token-limit overrides can be set independently. | | `instruction.append` | Text appended to the launched config's instruction. | | `max_instances` | Cap on active subagents spawned from this key. | | `archive_after_idle_minutes` | Archive subagents from this key once they have been idle this long. | | `max_subagents` | Top-level cap on active subagents across every key. | | `max_depth` | Top-level cap on how many levels of subagents can nest below this agent, at most 8. Defaults to 1, so subagents cannot spawn their own. The value is carried into every subagent config in the tree and cannot be raised by a subagent or by a profile it launches from. | Subagents start with a clean context: only the `task` passed to `spawn_agent` reaches them. Each subagent launches from a new config derived from its base with the key's overrides applied; a subagent at the tree's `max_depth` has `subagents` and `spawn_agent` removed. Subagents share the parent's attached machines, including its pool machines, instead of provisioning from the base config's `machine_sources`. A subagent's final answer, question, or failure arrives in the parent's timeline as a message from the subagent; `read_agent` reads its timeline on demand. Subagents appear in the console and API as agents with a `parent_agent_id`; listing agents returns top-level agents unless `include_subagents` or `parent_agent_id` is set. Archiving a parent archives its subagents. For a complete example you can deploy, with a `self` subagent whose copies each work in their own `git worktree` on the parent's machine, see [Coding agent in Slack](/examples/slack-coding-agent). ## Memory stores `memory_stores` attaches project-owned stores where agents can keep files between conversations or share them with other agents. Reference each store by name: ```yaml theme={null} memory_stores: - name: engineering access: read_write ``` Store names are unique within a project and use 1–64 lowercase letters, digits, and single hyphens between groups of letters or digits. Each attachment requires `access: read` or `access: read_write`. Agent writes require both attachment `access` and store `agent_access` to be `read_write`. Project managers can edit files in read-only stores. Attached stores are available through the [file tools](/tools/built-in#files) at `/memory//`. Paths can include nested directories. Files keep only their current contents; there is no edit history. Retrieval tools are added automatically; `read_write` attachments also add `storage_write`. Explicit tool disables still apply. The CLI's `omnara memory-stores` commands manage stores; `omnara memory-stores files` manages their files using a store ID and a path relative to its root. Replacing or deleting a file requires `--expected-digest`, returned by upload and download. ## Create a config > [tab] API > > The examples assume the client, `$ORG`/`orgID`, and `$PROJ`/`projectID` setup from the [quickstart](/quickstart). > > Send the config in `source` with its `source_format`. With `source_format: json`, `source` can be JSON text or the config as a JSON object. Either way, the source is stored exactly as sent. `POST /agent-configs` returns `201` for a new config or `200` with the existing config when the same source was already saved and still compiles to the same definition. Configs whose sources differ only in formatting get their own IDs but share one compiled definition and the same `effective_definition_hash`. Responses include the saved `source` text and its `source_format`. The CLI has no separate config step: pass the config to the command that uses it — `--file` takes a `.yaml`, `.yml`, or `.json` file, `--source` takes inline YAML or JSON, and `--config` references an existing `acfg_…` ID. > > ```bash CLI theme={null} > omnara agents launch --file agent.yaml --message "Get started." > omnara profiles create --name "Support triage" --file agent.yaml > ``` > > ```bash REST theme={null} > jq -n --rawfile src agent.yaml '{source: $src, source_format: "yaml"}' | > curl "$OMNARA_API/orgs/$ORG/projects/$PROJ/agent-configs" \ > -H "Authorization: Bearer $OMNARA_TOKEN" \ > -H "Content-Type: application/json" \ > -d @- > > # or send a JSON config as an object: > jq '{source: ., source_format: "json"}' agent.json | > curl "$OMNARA_API/orgs/$ORG/projects/$PROJ/agent-configs" \ > -H "Authorization: Bearer $OMNARA_TOKEN" \ > -H "Content-Type: application/json" \ > -d @- > ``` > > ```typescript SDK theme={null} > import { readFile } from 'node:fs/promises' > > const source = await readFile('agent.yaml', 'utf8') > > const config = await sdk.createAgentConfig({ > client, > path: { orgID, projectID }, > body: { source, source_format: 'yaml' }, > }) > console.log(config.data.id) > > // or pass the config as an object: > await sdk.createAgentConfig({ > client, > path: { orgID, projectID }, > body: { > source: { > instruction: 'You are a support triage assistant.', > model: { provider_config: 'omnara-openrouter', name: 'anthropic/claude-fable-5.1' }, > }, > source_format: 'json', > }, > }) > ``` > > The response includes the config ID and final model settings after config overrides and project limits are applied. Validation errors identify the field that needs to be fixed. > > > [!NOTE] > > > > Full schema and playground: [Create agent config](/api-reference/endpoints/configs-and-profiles/create-agent-config) · [Get agent config](/api-reference/endpoints/configs-and-profiles/get-agent-config). > [tab] Dashboard > > There is no separate config step in the dashboard: writing a config in the **Builder** or **YAML** editor while creating an [agent](/agents/overview#launch-an-agent) or an [agent profile](#create-a-profile) creates it when you submit. Validation errors surface inline in the editor, pointing at the offending field. ## Profiles A **profile** gives a config a stable name, such as *Support triage*. Launching from that profile uses the config it currently points to. Updating a profile points it to a new immutable config. Future launches use the new config; existing agents keep the config they already use. Previous versions remain available in the profile's history. ### Create a profile > [tab] API > > Point a name at an existing config: > > ```bash CLI theme={null} > omnara profiles create --name "Support triage" --config acfg_mv4qtrwz3jehcyd5n6a2bfgik7 > ``` > > ```bash REST theme={null} > curl "$OMNARA_API/orgs/$ORG/projects/$PROJ/agent-profiles" \ > -H "Authorization: Bearer $OMNARA_TOKEN" \ > -H "Content-Type: application/json" \ > -d '{ > "name": "Support triage", > "config": "acfg_mv4qtrwz3jehcyd5n6a2bfgik7" > }' > ``` > > ```typescript SDK theme={null} > const profile = await sdk.createAgentProfile({ > client, > path: { orgID, projectID }, > body: { > name: 'Support triage', > config: 'acfg_mv4qtrwz3jehcyd5n6a2bfgik7', > }, > }) > ``` > > The response starts with `current_generation: 1`; the generation increases each time the profile moves to a new config. With the CLI, pass `--file agent.yaml` instead of `--config` to create the config and the profile in one step. > > > [!NOTE] > > > > Full schema and playground: [Create agent profile](/api-reference/endpoints/configs-and-profiles/create-agent-profile). > [tab] Dashboard > > Select your project in the project switcher, open **Agents**, and click **New agent**. Enter a **Name**, then define the config in the **Builder** form or the **YAML** editor — the dashboard creates the config and the profile in one step. Click **Create profile**. ### Advance a profile to a new config > [tab] API > > With the REST API and the SDK, create the new config first, then update the profile, sending `expected_current_config_id` so the request fails with `409` if someone changed the profile after you read it. One CLI command does all of that — it creates the new config, reads the profile's current config, and points the profile at the new one: > > ```bash CLI theme={null} > omnara profiles update aprf_3jehcyd5n6a2bfgik7mv4qtrwz --file agent.yaml > ``` > > ```bash REST theme={null} > curl "$OMNARA_API/orgs/$ORG/projects/$PROJ/agent-profiles/aprf_3jehcyd5n6a2bfgik7mv4qtrwz/config" \ > -H "Authorization: Bearer $OMNARA_TOKEN" \ > -H "Content-Type: application/json" \ > -d '{ > "config": "acfg_n6a2bfgik7mv4qtrwz3jehcyd5", > "expected_current_config_id": "acfg_mv4qtrwz3jehcyd5n6a2bfgik7" > }' > ``` > > ```typescript SDK theme={null} > await sdk.updateAgentProfile({ > client, > path: { orgID, projectID, agentProfileID: 'aprf_3jehcyd5n6a2bfgik7mv4qtrwz' }, > body: { > config: 'acfg_n6a2bfgik7mv4qtrwz3jehcyd5', > expected_current_config_id: 'acfg_mv4qtrwz3jehcyd5n6a2bfgik7', > }, > }) > ``` > > With the CLI, pass `--expected-current-config-id acfg_…` to pin the expected version yourself. > > > [!NOTE] > > > > Full schema and playground: [Update agent profile](/api-reference/endpoints/configs-and-profiles/update-agent-profile). > [tab] Dashboard > > On **Agents → Profiles**, open the profile and select **Configuration**. Edit it in **Builder** or **YAML**, then click **Save revision**. The dashboard creates a new config and updates the profile. If someone else updated it first, your save fails instead of overwriting their change. ### Use or delete a profile To launch from a profile, click **Launch** on its card under **Agents → Profiles**, pass `--profile` to `omnara agents launch`, or pass `profile` to the API. Add `config` or `config_source` to run a different config under the profile's name. See [Launch an agent](/agents/overview#launch-an-agent). Deleting a profile removes the profile and its version history, but agents launched from it keep running. In the dashboard, open the profile's **Configuration** tab and click **Delete profile**, or run `omnara profiles delete {agent-profile-id}`. Integration settings can retain its public ID; future launches report the profile as unavailable until you choose an available profile. See [Delete agent profile](/api-reference/endpoints/configs-and-profiles/delete-agent-profile). ## Next > [card] [Agents](/agents/overview) > > Launch agents from the config you just created > [card] [Tools & permissions](/tools/permissions) > > Control which tools agents can use and which calls need approval # Agents Source: https://docs.omnara.com/agents/overview Launch agents, inspect them, cancel work, and archive This page covers common agent lifecycle operations in the dashboard, with the [CLI](/quickstart), and over the API. CLI examples assume you have run `omnara login` and `omnara config select`; API examples use a [bearer token](/api/authentication): ```bash theme={null} export OMNARA_API="https://api.omnara.com/v1" export BASE="$OMNARA_API/orgs/$ORG/projects/$PROJ" ``` ## Launch an agent Create an agent from a [config](/agents/configuration), or from a [profile](/agents/configuration#profiles) if you want the launch tied to a named template. Send at least one of `profile`, `config` (an existing config ID), or `config_source` (a config to compile and save during the launch, with the same `source` and `source_format` fields as creating a config), and not both `config` and `config_source`: * `profile` alone runs the profile's current config. * `config` or `config_source` alone runs that config. * `profile` with `config` or `config_source` attributes the agent to the profile but runs the given config, which need not be one of the profile's versions. The CLI takes `--profile`, and `--config`, `--file`, or `--source` for the config. > [tab] API > > ```bash CLI theme={null} > npx omnara agents launch \ > --config acfg_mv4qtrwz3jehcyd5n6a2bfgik7 \ > --name "Weekly report agent" \ > --message "Draft release notes for the changes merged this week." > > # or run a profile's current config: > npx omnara agents launch \ > --profile aprf_3jehcyd5n6a2bfgik7mv4qtrwz \ > --message "Draft release notes for the changes merged this week." > ``` > > ```bash REST theme={null} > curl "$BASE/agents" \ > -H "Authorization: Bearer $OMNARA_TOKEN" \ > -H "Content-Type: application/json" \ > -H "Idempotency-Key: launch-weekly-report-2026-08-03" \ > -d '{ > "config": "acfg_mv4qtrwz3jehcyd5n6a2bfgik7", > "name": "Weekly report agent", > "message": "Draft release notes for the changes merged this week." > }' > ``` > > ```typescript SDK theme={null} > const launched = await sdk.createAgent({ > client, > path: { orgID, projectID }, > headers: { 'Idempotency-Key': 'launch-weekly-report-2026-08-03' }, > body: { > config: 'acfg_mv4qtrwz3jehcyd5n6a2bfgik7', > name: 'Weekly report agent', > message: 'Draft release notes for the changes merged this week.', > }, > }) > const agentID = launched.data.agent.id > ``` > > > [!NOTE] > > > > Full schema and playground: [Create agent](/api-reference/endpoints/agents/create-agent). > [tab] Dashboard > > Select your project in the project switcher and open **Agents → Profiles**. Click > **Launch** on an existing [profile](/agents/configuration#profiles) to start an agent > with its current config. Users with launch-only access use this path. > > If your role can manage project resources, click **New agent** to define a new > profile. Enter a **Name**, configure it in **Builder** or **YAML**, then click > **Create & launch agent**. In the conversation view, use the composer to send its first task. ### Attach integrations and an initial input atomically The create-agent API accepts `tools` and `interaction_handlers` alongside `profile`, `config`, or `config_source`. They extend the config the launch would otherwise run, and the result is saved as a new config. They use the same names and capability selections as ordinary config authoring. Derivation adds missing entries and preserves existing entries as a whole, including explicit disabled tools. The base's pinned model and other identities are retained. The separate `subscriptions` array attaches integration-owned conversations to the new agent, using `integration_id` and `conversation`. The integration determines which activity is forwarded. Subscriptions alone leave the pinned config unchanged. Ordinary API attachments do not establish sending context. The shipped integration tools require context assigned by a provider or scheduled launch. Their interaction handlers use that same assigned conversation. Adding tools, handlers or subscriptions requires project management permission; operating an existing config requires only agent operate permission. Use `initial_input` instead of `message` to supply text blocks with an attributed actor. This example subscribes a new agent to events from one PR without changing its config: ```json theme={null} { "config": "acfg_mv4qtrwz3jehcyd5n6a2bfgik7", "subscriptions": [{ "integration_id": "YOUR_GITHUB_INTEGRATION_ID", "conversation": {"repository_id": 123456, "pull_request": 42} }], "initial_input": { "content_blocks": [{"type": "text", "text": "Track updates to pull request 42."}], "actor": {"provider_tenant_id": "review-service", "provider_user_id": "customer-7"} } } ``` User-authenticated requests omit `actor`; API keys may supply an external actor. Initial input supports queued text blocks; send inline media through the agent input endpoint after launch. The agent, subscriptions, initial input and any derived config commit together. A failure leaves none of those writes. The profile stays unchanged. Supply an `Idempotency-Key` to make retries safe: the first success returns `201` with the launch envelope, and reusing the key within the project returns `200` with only the current agent. The retry body is not applied, even if it has changed or an integration reference has since been deleted. ## Find agents The project's **Agents** page opens on **Profiles**; switch to the **Instances** tab to list active agents, and click one to open its conversation. `omnara agents list` and the API return active agents newest first; fetch one by ID with `omnara agents get {agent-id}`. Both responses include each agent's `activity`, whose `state` is `running`, `waiting_on_interaction`, `idle`, or `archived`. Alongside the state, `activity` always reports what else is open or in progress: `open_interactions` counts open questions and permission requests, `running_processes` lists commands the agent left running that will message it when they end, and `working_subagents` lists subagents that are still working. An agent can be `waiting_on_interaction` with a process running, or `idle` but about to wake when its build finishes. Archived agents are omitted from the list but remain retrievable by ID. > [!NOTE] > > For full schemas and playground see: [List agents](/api-reference/endpoints/agents/list-agents) and [Get agent](/api-reference/endpoints/agents/get-agent). ## Stop current work Stopping cancels the agent's current turn, pending tool calls, and open interactions. The agent and its conversation remain available; the next input starts a new turn. > [tab] API > > ```bash CLI theme={null} > npx omnara agents cancel agt_v4qtrwz3jehcyd5n6a2bfgik7m > ``` > > ```bash REST theme={null} > curl -X POST "$BASE/agents/agt_v4qtrwz3jehcyd5n6a2bfgik7m/cancel" \ > -H "Authorization: Bearer $OMNARA_TOKEN" > ``` > > ```typescript SDK theme={null} > const canceled = await sdk.cancelAgent({ > client, > path: { orgID, projectID, agentID: 'agt_v4qtrwz3jehcyd5n6a2bfgik7m' }, > }) > ``` > > The response includes `affected: true` when work was canceled. `affected: false` means the agent was already idle, so the request is safe to repeat. > > > [!NOTE] > > > > Full schema and playground: [Cancel agent](/api-reference/endpoints/agents/cancel-agent). > [tab] Dashboard > > While the agent is working, the composer shows **Stop agent** — click it. The button disappears when the agent is idle. ## Archive an agent Archiving retires an agent. It deletes its integration subscriptions, stops current work, drops queued inputs, releases machine bindings, and deletes pooled machines created for the agent. The agent leaves active listings, but its event history remains available by ID. Machines you connected yourself, configs, and profiles are not deleted. > [tab] API > > ```bash CLI theme={null} > npx omnara agents archive agt_v4qtrwz3jehcyd5n6a2bfgik7m > ``` > > ```bash REST theme={null} > curl -X POST "$BASE/agents/agt_v4qtrwz3jehcyd5n6a2bfgik7m/archive" \ > -H "Authorization: Bearer $OMNARA_TOKEN" > ``` > > ```typescript SDK theme={null} > const archived = await sdk.archiveAgent({ > client, > path: { orgID, projectID, agentID: 'agt_v4qtrwz3jehcyd5n6a2bfgik7m' }, > }) > ``` > > The response returns the agent with `state: "archived"`. Repeating the request is safe. > > > [!NOTE] > > > > Full schema and playground: [Archive agent](/api-reference/endpoints/agents/archive-agent). > [tab] Dashboard > > On the **Agents** page's **Instances** tab, open the agent's actions menu and click **Archive**. There is no unarchive. Launch a new agent when you need to continue with fresh work. ## Next > [card] [Agent configuration](/agents/configuration) > > Define an agent's model, tools, permissions, and machines > [card] [Sending input](/events/sending-input) > > Send messages and change direction while an agent works # Get a project actor Source: https://docs.omnara.com/api-reference/endpoints/actors/get-a-project-actor /api-reference/openapi.yaml get /orgs/{orgID}/projects/{projectID}/actors/{actorID} # List project actors Source: https://docs.omnara.com/api-reference/endpoints/actors/list-project-actors /api-reference/openapi.yaml get /orgs/{orgID}/projects/{projectID}/actors Items are ordered by created_at ascending, then id ascending. # Upsert an external actor Source: https://docs.omnara.com/api-reference/endpoints/actors/upsert-an-external-actor /api-reference/openapi.yaml put /orgs/{orgID}/projects/{projectID}/actors Upserts by (provider_tenant_id, provider_user_id). Omitted attributes keep their stored values; provided attributes are overwritten, including empty values. An unchanged actor is not rewritten. Actors written through this endpoint always have the external provider. # Archive agent Source: https://docs.omnara.com/api-reference/endpoints/agents/archive-agent /api-reference/openapi.yaml post /orgs/{orgID}/projects/{projectID}/agents/{agentID}/archive Archives the agent: requests cancellation of its active runtime, cancels queued inputs and affected processes, removes its pending wakeup, releases explicit machine bindings, deletes pooled machines created for the agent, and deletes cron triggers targeting the agent. The agent and its full event history stay readable by id; archived agents leave listings. The agent's profile and configuration are retained, as are explicitly attached BYO machines. Archiving an already-archived agent is a no-op that returns the agent unchanged. # Cancel agent Source: https://docs.omnara.com/api-reference/endpoints/agents/cancel-agent /api-reference/openapi.yaml post /orgs/{orgID}/projects/{projectID}/agents/{agentID}/cancel Stops the agent's current turn: cancels its unfinished tool calls and open interactions, stops processes whose run_command hasn't returned yet, and discards pending steering messages. The agent stays active and keeps its queued messages, and processes whose run_command already returned keep running. Returns affected false and changes nothing when no turn is in progress. # Cancel queued backlog input Source: https://docs.omnara.com/api-reference/endpoints/agents/cancel-queued-backlog-input /api-reference/openapi.yaml post /orgs/{orgID}/projects/{projectID}/agents/{agentID}/inputs/{inputID}/cancel # Create agent Source: https://docs.omnara.com/api-reference/endpoints/agents/create-agent /api-reference/openapi.yaml post /orgs/{orgID}/projects/{projectID}/agents Launch from a pinned config, optionally adding agent-local integration tools, subscriptions and interaction handlers and admitting an attributed initial text input in the same transaction. Adding integration capabilities requires project management permission; launching an existing config requires agent operate permission. Reusing an idempotency key returns the current agent even if the retry body differs. The retry body is not applied and integration references are not resolved again. # Create agent input Source: https://docs.omnara.com/api-reference/endpoints/agents/create-agent-input /api-reference/openapi.yaml post /orgs/{orgID}/projects/{projectID}/agents/{agentID}/inputs Sends a message to an agent and returns the stored input. Queued messages (the default) wait until the agent is ready for new work; steering messages reach it at its next model call. # Demote steering input to queued Source: https://docs.omnara.com/api-reference/endpoints/agents/demote-steering-input-to-queued /api-reference/openapi.yaml post /orgs/{orgID}/projects/{projectID}/agents/{agentID}/inputs/{inputID}/demote_to_queued # Get agent Source: https://docs.omnara.com/api-reference/endpoints/agents/get-agent /api-reference/openapi.yaml get /orgs/{orgID}/projects/{projectID}/agents/{agentID} Returns one agent's state, model, profile, current config ID, activity (running, waiting_on_interaction, idle, or archived), attached machines, and MCP connection states. The conversation isn't included. # Get agent usage Source: https://docs.omnara.com/api-reference/endpoints/agents/get-agent-usage /api-reference/openapi.yaml get /orgs/{orgID}/projects/{projectID}/agents/{agentID}/usage Tallies this agent's model token usage and provider-reported cost, broken down by configured model. Optionally limits the tally to a time window. # List agents Source: https://docs.omnara.com/api-reference/endpoints/agents/list-agents /api-reference/openapi.yaml get /orgs/{orgID}/projects/{projectID}/agents Lists the project's agents with their current activity, newest first by default. Archived agents and subagents are excluded unless requested. # List agents across projects Source: https://docs.omnara.com/api-reference/endpoints/agents/list-agents-across-projects /api-reference/openapi.yaml get /orgs/{orgID}/agents Lists agents in every project of the organization where the caller can read agents. Each agent carries its project_id. Items are ordered by created_at descending, then id descending. # List tool calls Source: https://docs.omnara.com/api-reference/endpoints/agents/list-tool-calls /api-reference/openapi.yaml get /orgs/{orgID}/projects/{projectID}/agents/{agentID}/tool-calls Items are ordered by created_at ascending, then id ascending. # List waiting backlog inputs Source: https://docs.omnara.com/api-reference/endpoints/agents/list-waiting-backlog-inputs /api-reference/openapi.yaml get /orgs/{orgID}/projects/{projectID}/agents/{agentID}/inputs/backlog Steering inputs are listed first, followed by queued inputs. Each group is ordered by input rank, queued_at ascending, then id ascending. # Move queued backlog input Source: https://docs.omnara.com/api-reference/endpoints/agents/move-queued-backlog-input /api-reference/openapi.yaml post /orgs/{orgID}/projects/{projectID}/agents/{agentID}/inputs/{inputID}/move # Promote queued input to steering Source: https://docs.omnara.com/api-reference/endpoints/agents/promote-queued-input-to-steering /api-reference/openapi.yaml post /orgs/{orgID}/projects/{projectID}/agents/{agentID}/inputs/{inputID}/promote_to_steering # Submit a result for a ready custom tool call Source: https://docs.omnara.com/api-reference/endpoints/agents/submit-a-result-for-a-ready-custom-tool-call /api-reference/openapi.yaml post /orgs/{orgID}/projects/{projectID}/agents/{agentID}/tool-calls/{toolCallID}/result Only ready custom tool calls accept externally supplied results. The first accepted result completes the call. Any later submission, or a result for a non-custom tool call, returns 409. # Update agent config Source: https://docs.omnara.com/api-reference/endpoints/agents/update-agent-config /api-reference/openapi.yaml post /orgs/{orgID}/projects/{projectID}/agents/{agentID}/config Replaces an active agent's config without restarting its conversation and returns the new agent_config. Model, instruction, and tool changes apply at the agent's next model call; machine changes apply immediately, stopping processes on removed machines and deleting the agent's machines from removed pools. Send the complete config, not a patch. # Create agent config Source: https://docs.omnara.com/api-reference/endpoints/configs-and-profiles/create-agent-config /api-reference/openapi.yaml post /orgs/{orgID}/projects/{projectID}/agent-configs Compiles and saves an immutable agent config (instruction, model, tools, machine_sources, and more) from YAML or JSON and returns it. Sending the same source again returns the existing config, as long as its names still resolve to the same resources. # Create agent profile Source: https://docs.omnara.com/api-reference/endpoints/configs-and-profiles/create-agent-profile /api-reference/openapi.yaml post /orgs/{orgID}/projects/{projectID}/agent-profiles Creates a named profile that points at an agent config and returns the profile with its current config. # Create cron trigger Source: https://docs.omnara.com/api-reference/endpoints/configs-and-profiles/create-cron-trigger /api-reference/openapi.yaml post /orgs/{orgID}/projects/{projectID}/cron-triggers Creates a cron trigger that fires on a schedule. A trigger targeting an agent profile launches a new agent from the profile's current config on each firing; a trigger targeting an agent sends an input to that agent. Missed firings are coalesced, matching standard cron behavior: if one or more scheduled times pass while firing is delayed, the trigger fires once for the oldest missed time and then resumes its schedule from the current time, without retroactively firing for the other missed times. # Delete agent profile Source: https://docs.omnara.com/api-reference/endpoints/configs-and-profiles/delete-agent-profile /api-reference/openapi.yaml delete /orgs/{orgID}/projects/{projectID}/agent-profiles/{agentProfileID} Deletes the profile, all of its profile versions, and any cron triggers targeting the profile. Agents launched from the profile are retained and keep running: each agent holds an immutable snapshot of the configuration it was launched with, so its reference to the profile becomes historical provenance only. Integration settings may retain the public profile ID; future launches fail as unavailable until those settings reference an available profile. # Delete cron trigger Source: https://docs.omnara.com/api-reference/endpoints/configs-and-profiles/delete-cron-trigger /api-reference/openapi.yaml delete /orgs/{orgID}/projects/{projectID}/cron-triggers/{cronTriggerID} Deletes a cron trigger so it never fires again; there is no undo. Agents it already launched keep running. # Get agent config Source: https://docs.omnara.com/api-reference/endpoints/configs-and-profiles/get-agent-config /api-reference/openapi.yaml get /orgs/{orgID}/projects/{projectID}/agent-configs/{agentConfigID} Returns one agent config: its compiled definition with names resolved to IDs, and its source if it was saved with one. Configs Omnara derives on its own, such as those for subagents and integration launches, have no source. # Get agent profile Source: https://docs.omnara.com/api-reference/endpoints/configs-and-profiles/get-agent-profile /api-reference/openapi.yaml get /orgs/{orgID}/projects/{projectID}/agent-profiles/{agentProfileID} Returns one agent profile with its current config, current_config_id, and current_generation. # Get agent profile usage Source: https://docs.omnara.com/api-reference/endpoints/configs-and-profiles/get-agent-profile-usage /api-reference/openapi.yaml get /orgs/{orgID}/projects/{projectID}/agent-profiles/{agentProfileID}/usage Tallies model token usage and provider-reported cost across the top-level agents launched from this profile, broken down by configured model. Usage from subagents those agents spawned is included only when `include_subagents` is true. Optionally limits the tally to a time window. # Get cron trigger Source: https://docs.omnara.com/api-reference/endpoints/configs-and-profiles/get-cron-trigger /api-reference/openapi.yaml get /orgs/{orgID}/projects/{projectID}/cron-triggers/{cronTriggerID} Returns one cron trigger: its target, schedule, timezone, message template, enabled flag, and firing state (last_fired_at, next_fire_at, and any failure since the last successful firing). # Get generated agent config source Source: https://docs.omnara.com/api-reference/endpoints/configs-and-profiles/get-generated-agent-config-source /api-reference/openapi.yaml get /orgs/{orgID}/projects/{projectID}/agent-configs/{agentConfigID}/generated-source Rebuilds a source from the config's compiled_definition, for configs saved without a source. # Get tool catalog Source: https://docs.omnara.com/api-reference/endpoints/configs-and-profiles/get-tool-catalog /api-reference/openapi.yaml get /tool-catalog Lists the built-in tools agent configs can use, with each tool's description and permission settings, plus the permission modes for custom and MCP tools. # List agent profiles Source: https://docs.omnara.com/api-reference/endpoints/configs-and-profiles/list-agent-profiles /api-reference/openapi.yaml get /orgs/{orgID}/projects/{projectID}/agent-profiles Lists the project's agent profiles, newest first by default, filterable by name. Items include a summary of each profile's current config, not its full compiled definition. # List agent profiles across projects Source: https://docs.omnara.com/api-reference/endpoints/configs-and-profiles/list-agent-profiles-across-projects /api-reference/openapi.yaml get /orgs/{orgID}/agent-profiles Lists agent profiles in every project of the organization the caller can read. Each profile carries its project_id. Items are ordered by created_at descending, then id descending. # List an MCP server's tools Source: https://docs.omnara.com/api-reference/endpoints/configs-and-profiles/list-an-mcp-servers-tools /api-reference/openapi.yaml post /orgs/{orgID}/projects/{projectID}/mcp-servers/tools Connects to a remote MCP server over streamable HTTP, negotiates the protocol version (stateless `server/discover` first, falling back to the `initialize` handshake for session-based servers), and returns the server's `tools/list` result. The request is not stored; use it to preview which tools a server exposes before referencing it from an agent config. Authentication mirrors the agent config `mcp_servers..auth` shape and resolves secrets available to the project. When the server does not speak MCP, the API responds 422 without an `auth` hint. When the server or its authorization server does not respond or fails, including a malformed MCP response or a JSON-RPC internal error from the server, the API responds 424; retrying may succeed once the server recovers. When the authorization server rejects the configured OAuth secret, the API responds 422 with an `oauth` hint so the caller can log in again. When Omnara times out reading or storing discovery state, the API responds 503. When the server rejects the connection with HTTP 401 or 403, the API probes the server's authorization requirements and responds 422 with an `auth` hint — `oauth` when the server advertises an OAuth authorization server, `bearer` when it expects a token but advertises no OAuth metadata. If that probe fails for a non-transient reason, the 422 carries no `auth` hint. # List cron triggers Source: https://docs.omnara.com/api-reference/endpoints/configs-and-profiles/list-cron-triggers /api-reference/openapi.yaml get /orgs/{orgID}/projects/{projectID}/cron-triggers Lists the project's cron triggers, newest first by default, filterable by name, agent, profile, or integration. Each item includes its schedule and next_fire_at. # Rename agent profile Source: https://docs.omnara.com/api-reference/endpoints/configs-and-profiles/rename-agent-profile /api-reference/openapi.yaml patch /orgs/{orgID}/projects/{projectID}/agent-profiles/{agentProfileID} Renames an agent profile without changing its config. Agents and cron triggers reference the profile by ID and keep working, but configs that name the profile under subagents must use the new name the next time they're compiled. # Resolve agent config tools Source: https://docs.omnara.com/api-reference/endpoints/configs-and-profiles/resolve-agent-config-tools /api-reference/openapi.yaml post /orgs/{orgID}/projects/{projectID}/agent-configs/tools Returns configured built-in and custom tools plus missing machine, skill, subagent, and retrieval defaults, including disabled entries. Accepts YAML/JSON source. Source previews validate tool-related fields only; they do not validate whether the config can be saved or launched. Does not resolve contextual integration tools, discover MCP tools, or change any configuration. # Search the MCP server registry Source: https://docs.omnara.com/api-reference/endpoints/configs-and-profiles/search-the-mcp-server-registry /api-reference/openapi.yaml get /mcp-servers Search the API's snapshot of the public MCP registry. The snapshot only holds active servers that publish a `streamable-http` remote. Results are ranked by relevance when `q` is set, otherwise ordered by name. # Update agent profile Source: https://docs.omnara.com/api-reference/endpoints/configs-and-profiles/update-agent-profile /api-reference/openapi.yaml post /orgs/{orgID}/projects/{projectID}/agent-profiles/{agentProfileID}/config Points a profile at a different agent config. Existing agents keep their current config; agents launched from the profile afterward use the new one. Fails with 409 unless expected_current_config_id matches the profile's current config. # Update cron trigger Source: https://docs.omnara.com/api-reference/endpoints/configs-and-profiles/update-cron-trigger /api-reference/openapi.yaml patch /orgs/{orgID}/projects/{projectID}/cron-triggers/{cronTriggerID} Updates the schedule, message template, or enabled state. The target is immutable; delete the trigger and create a new one to retarget. # Create an event webhook Source: https://docs.omnara.com/api-reference/endpoints/events/create-an-event-webhook /api-reference/openapi.yaml post /orgs/{orgID}/projects/{projectID}/event-webhooks Subscribes to future events from agents matching the scope. Each scope can have only one active webhook per URL; duplicates return 409. Matching scopes are additive. Requires project management permission. # Delete an event webhook Source: https://docs.omnara.com/api-reference/endpoints/events/delete-an-event-webhook /api-reference/openapi.yaml delete /orgs/{orgID}/projects/{projectID}/event-webhooks/{webhookID} Deletes the webhook and stops new deliveries and pending attempts. Requests already in flight may finish. # Get artifact Source: https://docs.omnara.com/api-reference/endpoints/events/get-artifact /api-reference/openapi.yaml get /orgs/{orgID}/projects/{projectID}/agents/{agentID}/artifacts/{artifactID} # Get artifact content Source: https://docs.omnara.com/api-reference/endpoints/events/get-artifact-content /api-reference/openapi.yaml get /orgs/{orgID}/projects/{projectID}/agents/{agentID}/artifacts/{artifactID}/content # List event webhooks for one scope Source: https://docs.omnara.com/api-reference/endpoints/events/list-event-webhooks-for-one-scope /api-reference/openapi.yaml get /orgs/{orgID}/projects/{projectID}/event-webhooks Returns the webhooks directly attached to the requested scope, not inherited matches. # List events Source: https://docs.omnara.com/api-reference/endpoints/events/list-events /api-reference/openapi.yaml get /orgs/{orgID}/projects/{projectID}/agents/{agentID}/events Returns one page of agent events, chronological within the page. Forward pagination (after_sequence) walks from a known boundary toward the present using next_after_sequence. Backward pagination (before_sequence) starts at the latest events and walks toward the beginning using next_before_sequence; passing before_sequence takes precedence over after_sequence. # List turn events Source: https://docs.omnara.com/api-reference/endpoints/events/list-turn-events /api-reference/openapi.yaml get /orgs/{orgID}/projects/{projectID}/agents/{agentID}/turns/{turnID}/events Returns one older event page for a turn, ordered chronologically within the page. Use the returned next_before_sequence as before_sequence to fetch older events. # List turns Source: https://docs.omnara.com/api-reference/endpoints/events/list-turns /api-reference/openapi.yaml get /orgs/{orgID}/projects/{projectID}/agents/{agentID}/turns Returns turns newest first. Use the returned next_before_turn_sequence as before_turn_sequence to fetch older turns. # Stream events Source: https://docs.omnara.com/api-reference/endpoints/events/stream-events /api-reference/openapi.yaml get /orgs/{orgID}/projects/{projectID}/agents/{agentID}/events/stream # Update event webhook settings Source: https://docs.omnara.com/api-reference/endpoints/events/update-event-webhook-settings /api-reference/openapi.yaml patch /orgs/{orgID}/projects/{projectID}/event-webhooks/{webhookID} Updates a webhook's URL, event filters, and signing settings. The scope cannot change. Omitted fields stay unchanged; set signing_secret_id to null to remove signing. Event filters affect newly queued deliveries; URL and signing changes also apply to pending attempts. # Attach an agent to an integration conversation Source: https://docs.omnara.com/api-reference/endpoints/integrations/attach-an-agent-to-an-integration-conversation /api-reference/openapi.yaml post /orgs/{orgID}/projects/{projectID}/integrations/{integrationID}/subscriptions Requires project management permission. Repeating an active attachment returns the same subscription. Subscriptions do not change the agent config or grant sending tools. # Connect an integration through OAuth Source: https://docs.omnara.com/api-reference/endpoints/integrations/connect-an-integration-through-oauth /api-reference/openapi.yaml post /orgs/{orgID}/projects/{projectID}/integrations/{integrationID}/oauth/setup Starts OAuth for this integration (currently Slack). The callback pins the integration and setup revision, records verified credentials, and returns integration_oauth=success and integration_id to the supplied local return_to route. It requires a browser session for the initiating user and current project management access. Replayed, stale, or deleted-integration callbacks are rejected. # Create a Slack provider app and connect it to an Omnara integration Source: https://docs.omnara.com/api-reference/endpoints/integrations/create-a-slack-provider-app-and-connect-it-to-an-omnara-integration /api-reference/openapi.yaml post /orgs/{orgID}/projects/{projectID}/integrations/{integrationID}/slack-setup Creates a provider-side Slack app using the supplied configuration token, name and optional icon, then starts OAuth for this saved Omnara integration. The callback activates this integration only. It never creates another Omnara integration or changes launcher settings. # Create an integration and optional launcher Source: https://docs.omnara.com/api-reference/endpoints/integrations/create-an-integration-and-optional-launcher /api-reference/openapi.yaml post /orgs/{orgID}/projects/{projectID}/integrations # Delete integration setup and revoke its capabilities Source: https://docs.omnara.com/api-reference/endpoints/integrations/delete-integration-setup-and-revoke-its-capabilities /api-reference/openapi.yaml delete /orgs/{orgID}/projects/{projectID}/integrations/{integrationID} Deletes the integration setup and its cron schedules, revokes its capabilities, and releases its credential reference. Existing agents, agent history, and provider messages are preserved. # Disconnect this integration without deleting its configuration Source: https://docs.omnara.com/api-reference/endpoints/integrations/disconnect-this-integration-without-deleting-its-configuration /api-reference/openapi.yaml post /orgs/{orgID}/projects/{projectID}/integrations/{integrationID}/disconnect # Get project integration Source: https://docs.omnara.com/api-reference/endpoints/integrations/get-project-integration /api-reference/openapi.yaml get /orgs/{orgID}/projects/{projectID}/integrations/{integrationID} # Inspect installations of a customer-owned GitHub App Source: https://docs.omnara.com/api-reference/endpoints/integrations/inspect-installations-of-a-customer-owned-github-app /api-reference/openapi.yaml post /orgs/{orgID}/projects/{projectID}/integrations/{integrationID}/github-setup/installations Verifies the supplied project-available GitHub credential secret using App authentication and returns one bounded page of installations. Requires a browser session and project management access. Inspection does not connect or change this integration. Explicit confirmation uses ConfigureIntegration with the returned provider_app_id, selected installation ID, credential secret ID and current expected setup revision. A saved secret remains usable while organization approval is pending; there is no expiring continuation. Guided links support github.com only. # List installed integration implementations and their capabilities Source: https://docs.omnara.com/api-reference/endpoints/integrations/list-installed-integration-implementations-and-their-capabilities /api-reference/openapi.yaml get /orgs/{orgID}/projects/{projectID}/integration-definitions # List integration conversation subscriptions Source: https://docs.omnara.com/api-reference/endpoints/integrations/list-integration-conversation-subscriptions /api-reference/openapi.yaml get /orgs/{orgID}/projects/{projectID}/integrations/{integrationID}/subscriptions Requires project read permission. Disconnected integrations retain their subscriptions and support listing. # List integrations across projects Source: https://docs.omnara.com/api-reference/endpoints/integrations/list-integrations-across-projects /api-reference/openapi.yaml get /orgs/{orgID}/integrations Lists integrations in every project of the organization the caller can read. Each integration carries its project_id. Items are ordered by created_at descending, then id descending. # List project integrations Source: https://docs.omnara.com/api-reference/endpoints/integrations/list-project-integrations /api-reference/openapi.yaml get /orgs/{orgID}/projects/{projectID}/integrations # List repositories a GitHub integration can access Source: https://docs.omnara.com/api-reference/endpoints/integrations/list-repositories-a-github-integration-can-access /api-reference/openapi.yaml get /orgs/{orgID}/projects/{projectID}/integrations/{integrationID}/github-repositories Returns one page of up to 100 repositories the connected installation can access, for use in git_credentials.repository_ids. Requires project management access. # Start registration of a customer-owned GitHub App Source: https://docs.omnara.com/api-reference/endpoints/integrations/start-registration-of-a-customer-owned-github-app /api-reference/openapi.yaml post /orgs/{orgID}/projects/{projectID}/integrations/{integrationID}/github-setup Starts guided registration on github.com for a never-connected GitHub integration. Requires a browser session and project management access. Submit manifest as a JSON-encoded form field to registration_url. The callback saves a project credential secret and redirects to the canonical integration page with github_setup=credentials_saved and credential_secret_id (a public secret ID). It does not connect an installation or change launcher settings. # Stop forwarding an integration conversation to an agent Source: https://docs.omnara.com/api-reference/endpoints/integrations/stop-forwarding-an-integration-conversation-to-an-agent /api-reference/openapi.yaml delete /orgs/{orgID}/projects/{projectID}/integrations/{integrationID}/subscriptions/{subscriptionID} Requires project management permission. Missing subscriptions are a no-op within an existing integration. Disconnected integrations support deletion. Stops future incoming forwarding for this subscription. The agent, sending tools, interaction handlers, and history remain available. Attaching the conversation again through the API resumes forwarding with a new subscription ID. # Update integration settings Source: https://docs.omnara.com/api-reference/endpoints/integrations/update-integration-settings /api-reference/openapi.yaml put /orgs/{orgID}/projects/{projectID}/integrations/{integrationID} Replaces integration-owned settings while preserving credentials. The request contains only settings; name and integration_kind are immutable and accepted only on create. Preserve integration-level fields when editing the launcher. Settings follow capabilities.settings.input_schema from the integration catalog. # Verify and configure integration credentials Source: https://docs.omnara.com/api-reference/endpoints/integrations/verify-and-configure-integration-credentials /api-reference/openapi.yaml post /orgs/{orgID}/projects/{projectID}/integrations/{integrationID}/setup Requires a user principal with project management permission. Slack credentials are configured through OAuth. A stale expected_setup_revision or a credential changed during verification returns 409; reload the integration and retry setup. Setup preserves integration settings; verified provider identity scopes incoming events. # List agent interactions Source: https://docs.omnara.com/api-reference/endpoints/interactions/list-agent-interactions /api-reference/openapi.yaml get /orgs/{orgID}/projects/{projectID}/agents/{agentID}/interactions Lists an agent's permission prompts and ask_question questions, oldest first, with each one's form and answers. state filters by open, resolved, or canceled. # Resolve agent interaction Source: https://docs.omnara.com/api-reference/endpoints/interactions/resolve-agent-interaction /api-reference/openapi.yaml post /orgs/{orgID}/projects/{projectID}/agents/{agentID}/interactions/{interactionID}/resolve Answers an open permission prompt or question so the paused agent resumes, and returns the resolved interaction. Send one answer per question with zero-based option_indices; permission prompts use 0 for Allow and 1 for Deny. # Check machine pool connection Source: https://docs.omnara.com/api-reference/endpoints/machine-pools/check-machine-pool-connection /api-reference/openapi.yaml post /orgs/{orgID}/machine-pools/check-connection Checks a tenant-managed pool's provider credential and provider config by making read-only requests to the provider. # Create machine pool Source: https://docs.omnara.com/api-reference/endpoints/machine-pools/create-machine-pool /api-reference/openapi.yaml post /orgs/{orgID}/machine-pools # Create project machine pool grant Source: https://docs.omnara.com/api-reference/endpoints/machine-pools/create-project-machine-pool-grant /api-reference/openapi.yaml post /orgs/{orgID}/projects/{projectID}/machine-pool-grants Lets a project create machines from a machine pool, optionally with its own defaults and usage caps within the pool's limits, and returns the grant. No machines are created by this call. # Delete machine pool Source: https://docs.omnara.com/api-reference/endpoints/machine-pools/delete-machine-pool /api-reference/openapi.yaml delete /orgs/{orgID}/machine-pools/{poolID} Deletes the machine pool and its pooled machines, revokes generated machine grants and project machine-pool grants, and stops processes that depended on those grants. Deleting a cluster-managed machine pool is rejected with 409 Conflict; there are no other dependency blockers. # Delete project machine pool grant Source: https://docs.omnara.com/api-reference/endpoints/machine-pools/delete-project-machine-pool-grant /api-reference/openapi.yaml delete /orgs/{orgID}/projects/{projectID}/machine-pool-grants/{poolGrantID} Removes the project's access to the machine pool, revokes machine grants generated through it, and stops processes that depended on those grants. The pool itself is retained, and there are no dependency blockers. # Get machine pool Source: https://docs.omnara.com/api-reference/endpoints/machine-pools/get-machine-pool /api-reference/openapi.yaml get /orgs/{orgID}/machine-pools/{poolID} Returns one machine pool's configuration and current usage: provider, default machine settings, CPU, memory, and machine-count limits, and idle deletion policy. # Get project machine pool grant Source: https://docs.omnara.com/api-reference/endpoints/machine-pools/get-project-machine-pool-grant /api-reference/openapi.yaml get /orgs/{orgID}/projects/{projectID}/machine-pool-grants/{poolGrantID} Returns one project's grant for a machine pool, with the defaults and limits it sets on top of the pool's own. Null fields set no override, so the pool's value applies. # List machine pools Source: https://docs.omnara.com/api-reference/endpoints/machine-pools/list-machine-pools /api-reference/openapi.yaml get /orgs/{orgID}/machine-pools List machine pools with server-side filtering, sorting, and cursor pagination. # List project machine pool grants Source: https://docs.omnara.com/api-reference/endpoints/machine-pools/list-project-machine-pool-grants /api-reference/openapi.yaml get /orgs/{orgID}/projects/{projectID}/machine-pool-grants Lists the machine pools a project can create machines from, one grant per pool, most recently granted first by default. # Update machine pool Source: https://docs.omnara.com/api-reference/endpoints/machine-pools/update-machine-pool /api-reference/openapi.yaml put /orgs/{orgID}/machine-pools/{poolID} Tenant-managed pools support every request field. Cluster-managed pools only allow changes to default CPU and memory, environment and secret environment variables, provider-supported per-machine CPU and memory limits, and the idle deletion policy. Other cluster-managed changes are rejected with 409 Conflict. # Update project machine pool grant Source: https://docs.omnara.com/api-reference/endpoints/machine-pools/update-project-machine-pool-grant /api-reference/openapi.yaml patch /orgs/{orgID}/projects/{projectID}/machine-pool-grants/{poolGrantID} Updates the grant's overrides and limits. Omitted fields keep their current values; null clears an override so the grant follows the machine pool again. Overlay objects and metadata are replaced whole when present. Existing machines keep their configuration; the updated defaults and limits apply to new launches. # Connect a BYO machine Source: https://docs.omnara.com/api-reference/endpoints/machines/connect-a-byo-machine /api-reference/openapi.yaml post /orgs/{orgID}/machines/connect Registers a machine you run yourself and returns its daemon token for installing omnarad on it; the token is shown only in this response. The machine stays offline until its daemon connects. project_ids grants it to those projects in the same call, and their agent configs reference it by display_name. # Create byo machine daemon token Source: https://docs.omnara.com/api-reference/endpoints/machines/create-byo-machine-daemon-token /api-reference/openapi.yaml post /orgs/{orgID}/machines/{machineID}/daemon-tokens # Create machine Source: https://docs.omnara.com/api-reference/endpoints/machines/create-machine /api-reference/openapi.yaml post /orgs/{orgID}/machines # Create project machine grant Source: https://docs.omnara.com/api-reference/endpoints/machines/create-project-machine-grant /api-reference/openapi.yaml post /orgs/{orgID}/projects/{projectID}/machine-grants Lets a project's agent configs use one BYO machine by name and returns the grant with the machine's record. Pooled machines can't be granted this way and return 404. # Delete machine Source: https://docs.omnara.com/api-reference/endpoints/machines/delete-machine /api-reference/openapi.yaml delete /orgs/{orgID}/machines/{machineID} Deletes a BYO machine, ends its active daemon runtimes and work, revokes its daemon tokens and project grants, and releases agent bindings. Pooled machines are managed through their machine pool and cannot be deleted through this route. There are no dependency blockers for a BYO machine. # Delete project machine grant Source: https://docs.omnara.com/api-reference/endpoints/machines/delete-project-machine-grant /api-reference/openapi.yaml delete /orgs/{orgID}/projects/{projectID}/machine-grants/{grantID} Removes the project's access to the machine. Processes that depended on the grant are stopped. The machine itself is retained, and there are no dependency blockers. # Get machine Source: https://docs.omnara.com/api-reference/endpoints/machines/get-machine /api-reference/openapi.yaml get /orgs/{orgID}/machines/{machineID} Returns one active machine's full record: connection state, execution defaults (secret IDs only, never values), metadata, and the latest failure report. Machines in other lifecycle states, such as pool machines still provisioning, aren't returned. # List byo machine daemon tokens Source: https://docs.omnara.com/api-reference/endpoints/machines/list-byo-machine-daemon-tokens /api-reference/openapi.yaml get /orgs/{orgID}/machines/{machineID}/daemon-tokens Items are ordered by created_at descending, then id descending. # List project machine grants Source: https://docs.omnara.com/api-reference/endpoints/machines/list-project-machine-grants /api-reference/openapi.yaml get /orgs/{orgID}/projects/{projectID}/machine-grants Lists explicit machine grants that can be deleted directly. Pool-derived machine access is managed through project machine pool grants. # List visible machines Source: https://docs.omnara.com/api-reference/endpoints/machines/list-visible-machines /api-reference/openapi.yaml get /orgs/{orgID}/machines Lists the machines the caller can see, both BYO and pooled, newest first by default, with each one's connection state. Items are summaries without execution defaults or metadata. # List visible project machines Source: https://docs.omnara.com/api-reference/endpoints/machines/list-visible-project-machines /api-reference/openapi.yaml get /orgs/{orgID}/projects/{projectID}/machines Items are ordered by machine created_at descending, then machine id descending. # Revoke machine daemon token Source: https://docs.omnara.com/api-reference/endpoints/machines/revoke-machine-daemon-token /api-reference/openapi.yaml post /orgs/{orgID}/machines/{machineID}/daemon-tokens/{tokenID}/revoke # Update machine execution defaults Source: https://docs.omnara.com/api-reference/endpoints/machines/update-machine-execution-defaults /api-reference/openapi.yaml patch /orgs/{orgID}/machines/{machineID} Updates a machine's execution defaults: working directory, environment variables, and secret environment variables. env and secret_env replace the whole map, and changes apply to processes started afterward. # Create memory store Source: https://docs.omnara.com/api-reference/endpoints/memory-stores/create-memory-store /api-reference/openapi.yaml post /orgs/{orgID}/projects/{projectID}/memory-stores # Delete memory file Source: https://docs.omnara.com/api-reference/endpoints/memory-stores/delete-memory-file /api-reference/openapi.yaml delete /orgs/{orgID}/projects/{projectID}/memory-stores/{memoryStoreID}/file # Delete memory store Source: https://docs.omnara.com/api-reference/endpoints/memory-stores/delete-memory-store /api-reference/openapi.yaml delete /orgs/{orgID}/projects/{projectID}/memory-stores/{memoryStoreID} # Download memory file Source: https://docs.omnara.com/api-reference/endpoints/memory-stores/download-memory-file /api-reference/openapi.yaml get /orgs/{orgID}/projects/{projectID}/memory-stores/{memoryStoreID}/file # Get memory store Source: https://docs.omnara.com/api-reference/endpoints/memory-stores/get-memory-store /api-reference/openapi.yaml get /orgs/{orgID}/projects/{projectID}/memory-stores/{memoryStoreID} # List memory files Source: https://docs.omnara.com/api-reference/endpoints/memory-stores/list-memory-files /api-reference/openapi.yaml get /orgs/{orgID}/projects/{projectID}/memory-stores/{memoryStoreID}/files List a directory's immediate children in name order. Omit path to list the store root. # List memory stores Source: https://docs.omnara.com/api-reference/endpoints/memory-stores/list-memory-stores /api-reference/openapi.yaml get /orgs/{orgID}/projects/{projectID}/memory-stores # List memory stores across projects Source: https://docs.omnara.com/api-reference/endpoints/memory-stores/list-memory-stores-across-projects /api-reference/openapi.yaml get /orgs/{orgID}/memory-stores Lists memory stores in every project of the organization the caller can read. Each store carries its project_id. Items are ordered by name, then id. # Update memory store Source: https://docs.omnara.com/api-reference/endpoints/memory-stores/update-memory-store /api-reference/openapi.yaml patch /orgs/{orgID}/projects/{projectID}/memory-stores/{memoryStoreID} # Write memory file Source: https://docs.omnara.com/api-reference/endpoints/memory-stores/write-memory-file /api-reference/openapi.yaml put /orgs/{orgID}/projects/{projectID}/memory-stores/{memoryStoreID}/file # Create configured model Source: https://docs.omnara.com/api-reference/endpoints/models/create-configured-model /api-reference/openapi.yaml post /orgs/{orgID}/model-provider-configs/{modelProviderConfigID}/models Adds a provider model to this provider config under the name agent configs use as model.name, and returns it. The model slug isn't checked against the provider's catalog, and no project can use the model yet. # Create model provider config Source: https://docs.omnara.com/api-reference/endpoints/models/create-model-provider-config /api-reference/openapi.yaml post /orgs/{orgID}/model-provider-configs Create a model provider config, validate the configured credential where the provider exposes a validation endpoint, then probe the provider's /models endpoint. The result is returned as model_catalog; a failed probe does not fail creation. # Create project model grant Source: https://docs.omnara.com/api-reference/endpoints/models/create-project-model-grant /api-reference/openapi.yaml post /orgs/{orgID}/projects/{projectID}/model-grants Lets a project's agent configs use a configured model and returns the new grant. Optional overrides can narrow the model for that project (lower token limits, tools or reasoning turned off, fewer reasoning efforts or modalities) and set its per-request defaults within those limits. # Delete configured model Source: https://docs.omnara.com/api-reference/endpoints/models/delete-configured-model /api-reference/openapi.yaml delete /orgs/{orgID}/model-provider-configs/{modelProviderConfigID}/models/{configuredModelID} Deletes only the configured model. Deletion is blocked while it has active project grants. Cluster-managed configured models cannot be deleted through this route. # Delete model provider config Source: https://docs.omnara.com/api-reference/endpoints/models/delete-model-provider-config /api-reference/openapi.yaml delete /orgs/{orgID}/model-provider-configs/{modelProviderConfigID} Deletes only the model provider config. Deletion is blocked while it has configured models. Cluster-managed provider configs cannot be deleted through this route. # Delete project model grant Source: https://docs.omnara.com/api-reference/endpoints/models/delete-project-model-grant /api-reference/openapi.yaml delete /orgs/{orgID}/projects/{projectID}/model-grants/{modelGrantID} Removes the project's access to the configured model. The grant is live policy: new launches can no longer select the model, and agents already using it lose access on their next model call. The model itself is retained, and there are no dependency blockers. # Get model catalog Source: https://docs.omnara.com/api-reference/endpoints/models/get-model-catalog /api-reference/openapi.yaml get /orgs/{orgID}/model-provider-configs/{modelProviderConfigID}/model-catalog Fetches the provider's live model catalog by validating the configured credential and probing the provider's /models endpoint. The call waits on the upstream provider, so it is slower than the other model provider config routes and can report failure as data. # Get model provider config Source: https://docs.omnara.com/api-reference/endpoints/models/get-model-provider-config /api-reference/openapi.yaml get /orgs/{orgID}/model-provider-configs/{modelProviderConfigID} Returns one model provider config's stored settings; secret values are never returned. The provider isn't contacted, so the response doesn't list its models. # List configured models Source: https://docs.omnara.com/api-reference/endpoints/models/list-configured-models /api-reference/openapi.yaml get /orgs/{orgID}/model-provider-configs/{modelProviderConfigID}/models Lists the models configured under one model provider config, newest first, with each model's slug, token limits, and reasoning support. # List model provider configs Source: https://docs.omnara.com/api-reference/endpoints/models/list-model-provider-configs /api-reference/openapi.yaml get /orgs/{orgID}/model-provider-configs List model provider configs with server-side filtering, sorting, and cursor pagination. # List project model grants Source: https://docs.omnara.com/api-reference/endpoints/models/list-project-model-grants /api-reference/openapi.yaml get /orgs/{orgID}/projects/{projectID}/model-grants Lists the configured models a project can use, most recently granted first by default, with each grant's overrides and the model's name and provider config. # Update configured model Source: https://docs.omnara.com/api-reference/endpoints/models/update-configured-model /api-reference/openapi.yaml put /orgs/{orgID}/model-provider-configs/{modelProviderConfigID}/models/{configuredModelID} Updates a configured model's settings and returns the updated model; omitted fields keep their values. Agents already using the model pick up the change on their next model call. The update isn't checked against project grants or agent configs, so lowering a limit below their overrides or turning off tools or reasoning they rely on makes those agents' next model call fail. # Update model provider config Source: https://docs.omnara.com/api-reference/endpoints/models/update-model-provider-config /api-reference/openapi.yaml put /orgs/{orgID}/model-provider-configs/{modelProviderConfigID} Updates a model provider config's connection settings, such as its credential and headers, and returns the updated config; omitted fields keep their values. The credential secret is checked to exist and match auth_kind, but the provider isn't contacted, so a credential the provider rejects isn't caught here. # Update project model grant Source: https://docs.omnara.com/api-reference/endpoints/models/update-project-model-grant /api-reference/openapi.yaml patch /orgs/{orgID}/projects/{projectID}/model-grants/{modelGrantID} Updates the project's overrides for the configured model. Omitted fields keep their current values; null, or [] for supported_reasoning_efforts, input_modalities, and output_modalities, clears an override so the project inherits from the configured model. Agents already using the model apply the updated overrides on their next model call. Updates are validated against the configured model's current revision but not against agent configs, so narrowing below a config's model settings, such as its context_window_tokens or reasoning effort, makes that agent's next model call fail. # Create org invitation Source: https://docs.omnara.com/api-reference/endpoints/organizations-and-projects/create-org-invitation /api-reference/openapi.yaml post /orgs/{orgID}/invitations # Create organization Source: https://docs.omnara.com/api-reference/endpoints/organizations-and-projects/create-organization /api-reference/openapi.yaml post /orgs Creates the organization and its local default resources atomically. An idempotency key determines the proposed organization ID; replaying it returns the existing organization. Without a key, each request is a fresh attempt. Optional default-provider provisioning begins after commit and retries asynchronously without failing organization creation. # Create project Source: https://docs.omnara.com/api-reference/endpoints/organizations-and-projects/create-project /api-reference/openapi.yaml post /orgs/{orgID}/projects Creates a project, makes the caller its admin, and returns it. The new project has no grants to organization models, machines, skills, or secrets. # Delete org invitation Source: https://docs.omnara.com/api-reference/endpoints/organizations-and-projects/delete-org-invitation /api-reference/openapi.yaml delete /orgs/{orgID}/invitations/{invitationID} Deletes the pending invitation so it can no longer be accepted. # Delete organization Source: https://docs.omnara.com/api-reference/endpoints/organizations-and-projects/delete-organization /api-reference/openapi.yaml delete /orgs/{orgID} Deletes the organization and everything in it: projects, memberships, pending invitations, profiles, integrations, machine pools and machines, model provider configs and configured models, and all skills. Agents are archived and their runtimes and queued work are stopped. Secrets are permanently deleted once machine teardown no longer needs them. Deletion fails if project agent teardown cannot complete. Only organization owners can delete an organization. # Delete project Source: https://docs.omnara.com/api-reference/endpoints/organizations-and-projects/delete-project /api-reference/openapi.yaml delete /orgs/{orgID}/projects/{projectID} Deletes the project, its memberships, profiles and profile versions, integrations and their interaction targets, project-owned skills and revisions, and grants owned by or targeting the project. Project-owned secrets are permanently deleted with their versions and grants. The project's agents are archived and their runtimes and queued work are stopped. Machine, machine-pool, and model grants are removed without deleting the shared machine, pool, or model. Deletion fails if agent teardown cannot complete. # Get org usage Source: https://docs.omnara.com/api-reference/endpoints/organizations-and-projects/get-org-usage /api-reference/openapi.yaml get /orgs/{orgID}/usage Tallies model token usage and provider-reported cost across every project the caller can read in the organization, broken down by configured model. Optionally limits the tally to a time window or to a subset of those projects; including a project the caller can't read is not found. # Get project usage Source: https://docs.omnara.com/api-reference/endpoints/organizations-and-projects/get-project-usage /api-reference/openapi.yaml get /orgs/{orgID}/projects/{projectID}/usage Tallies model token usage and provider-reported cost across every agent in the project, including subagents, broken down by configured model. Optionally limits the tally to a time window. # Get usage timeseries Source: https://docs.omnara.com/api-reference/endpoints/organizations-and-projects/get-usage-timeseries /api-reference/openapi.yaml get /orgs/{orgID}/usage/timeseries Buckets model usage over time for one metric, optionally broken down by configured model or agent profile. Covers every project the caller can read in the organization; `project_ids` and `agent_profile_ids` narrow that scope. Buckets are calendar days, weeks (starting Monday), or months in `timezone`. # List a member's explicit project role grants Source: https://docs.omnara.com/api-reference/endpoints/organizations-and-projects/list-a-members-explicit-project-role-grants /api-reference/openapi.yaml get /orgs/{orgID}/members/{userID}/projects Lists the member's explicit per-project role grants. Org owners and admins implicitly have admin access to every project and will not appear here for those projects. # List org invitations Source: https://docs.omnara.com/api-reference/endpoints/organizations-and-projects/list-org-invitations /api-reference/openapi.yaml get /orgs/{orgID}/invitations Every listed invitation is pending: accepting, declining, revoking, or organization deletion removes the invitation. Items are ordered by created_at descending, then id descending. # List organization members Source: https://docs.omnara.com/api-reference/endpoints/organizations-and-projects/list-organization-members /api-reference/openapi.yaml get /orgs/{orgID}/members List organization members with server-side display-name filtering, sorting, and cursor pagination. # List the authenticated principal's organizations Source: https://docs.omnara.com/api-reference/endpoints/organizations-and-projects/list-the-authenticated-principals-organizations /api-reference/openapi.yaml get /orgs Users get every organization they belong to; org API keys get the organization that issued them. Items are ordered by organization name ascending. # List visible projects Source: https://docs.omnara.com/api-reference/endpoints/organizations-and-projects/list-visible-projects /api-reference/openapi.yaml get /orgs/{orgID}/projects Lists the projects the caller can access in the organization, newest first, with the caller's permissions on each. # Remove a member's project role grant Source: https://docs.omnara.com/api-reference/endpoints/organizations-and-projects/remove-a-members-project-role-grant /api-reference/openapi.yaml delete /orgs/{orgID}/members/{userID}/projects/{projectID} Removes only the member's explicit role grant for this project. No other memberships, grants, or resources are deleted, and there are no dependency blockers. # Remove org member Source: https://docs.omnara.com/api-reference/endpoints/organizations-and-projects/remove-org-member /api-reference/openapi.yaml delete /orgs/{orgID}/members/{userID} Removes the user's organization membership and all project memberships in the organization. The member's personal secrets and skills in the organization are deleted with the membership, and pending invitations they sent are canceled. Only owners can remove an owner. Removal is blocked when the user is the organization's last owner, while a personal skill is referenced by an active agent's configuration, or while a personal secret is referenced by a model provider config, machine pool, or integration. # Set a member's role on a project Source: https://docs.omnara.com/api-reference/endpoints/organizations-and-projects/set-a-members-role-on-a-project /api-reference/openapi.yaml put /orgs/{orgID}/members/{userID}/projects/{projectID} Creates or updates the member's explicit role grant on the project. # Update org member role Source: https://docs.omnara.com/api-reference/endpoints/organizations-and-projects/update-org-member-role /api-reference/openapi.yaml patch /orgs/{orgID}/members/{userID} Changes the member's org role. Only owners can grant the owner role or change an owner's role, and the last owner cannot be demoted. Demoting an owner cancels the pending owner invitations they sent. # Create secret Source: https://docs.omnara.com/api-reference/endpoints/secrets/create-secret /api-reference/openapi.yaml post /orgs/{orgID}/secrets Creates an encrypted secret holding one credential and returns its metadata; the value is never returned. owner sets whether the organization, a project, or the caller owns it. # Create secret grant Source: https://docs.omnara.com/api-reference/endpoints/secrets/create-secret-grant /api-reference/openapi.yaml post /orgs/{orgID}/secrets/{secretID}/grants Shares a secret with another project so that project's resources can use it, and returns the grant; ownership doesn't change. # Create secret version Source: https://docs.omnara.com/api-reference/endpoints/secrets/create-secret-version /api-reference/openapi.yaml post /orgs/{orgID}/secrets/{secretID}/versions # Delete secret Source: https://docs.omnara.com/api-reference/endpoints/secrets/delete-secret /api-reference/openapi.yaml delete /orgs/{orgID}/secrets/{secretID} Deletes the secret and permanently destroys all of its versions and project grants; the ciphertext cannot be recovered. Deletion is blocked while the secret is the credential of a model provider config, machine pool, or integration. # Delete secret grant Source: https://docs.omnara.com/api-reference/endpoints/secrets/delete-secret-grant /api-reference/openapi.yaml delete /orgs/{orgID}/secrets/{secretID}/grants/{grantID} Deletes only this project grant. The secret, its versions, the target project, and other grants are retained, and there are no dependency blockers. # Get secret available to project Source: https://docs.omnara.com/api-reference/endpoints/secrets/get-secret-available-to-project /api-reference/openapi.yaml get /orgs/{orgID}/projects/{projectID}/secrets/{secretID} # Get secret through ownership authority Source: https://docs.omnara.com/api-reference/endpoints/secrets/get-secret-through-ownership-authority /api-reference/openapi.yaml get /orgs/{orgID}/secrets/{secretID} Returns a secret's owner, kind, name, and current version; the secret value is never returned. # List secret grants Source: https://docs.omnara.com/api-reference/endpoints/secrets/list-secret-grants /api-reference/openapi.yaml get /orgs/{orgID}/secrets/{secretID}/grants Lists the projects a secret is shared with, one grant per project, most recently shared first by default. # List secrets available to project Source: https://docs.omnara.com/api-reference/endpoints/secrets/list-secrets-available-to-project /api-reference/openapi.yaml get /orgs/{orgID}/projects/{projectID}/secrets Includes directly owned and granted secrets, ordered by created_at descending, then id descending. # List secrets visible through ownership authority Source: https://docs.omnara.com/api-reference/endpoints/secrets/list-secrets-visible-through-ownership-authority /api-reference/openapi.yaml get /orgs/{orgID}/secrets Items are ordered by created_at descending, then id descending. Without an owner filter, returns the deduplicated union of organization secrets the caller may list, project secrets the caller may list, and the caller's own user secrets. # Start MCP OAuth for a new secret Source: https://docs.omnara.com/api-reference/endpoints/secrets/start-mcp-oauth-for-a-new-secret /api-reference/openapi.yaml post /orgs/{orgID}/secrets/mcp-oauth # Update secret metadata Source: https://docs.omnara.com/api-reference/endpoints/secrets/update-secret-metadata /api-reference/openapi.yaml patch /orgs/{orgID}/secrets/{secretID} Renames a secret or replaces its metadata, and returns the updated secret without its value. The value, kind, and owner can't be changed here. References use the secret ID, so renaming doesn't break them. # Create skill Source: https://docs.omnara.com/api-reference/endpoints/skills/create-skill /api-reference/openapi.yaml post /orgs/{orgID}/skills Uploads a skill archive. If a skill with the archive's name already exists for the requested owner, the upload becomes the skill's next revision. The owner is immutable after creation. # Create skill grant Source: https://docs.omnara.com/api-reference/endpoints/skills/create-skill-grant /api-reference/openapi.yaml post /orgs/{orgID}/skills/{skillID}/grants Shares a skill with another project so its agent configs can attach it, and returns the grant; ownership doesn't change. # Delete skill Source: https://docs.omnara.com/api-reference/endpoints/skills/delete-skill /api-reference/openapi.yaml delete /orgs/{orgID}/skills/{skillID} Deletes the skill together with all of its revisions and project grants; uploaded archive content is destroyed. No other resources are deleted. Deletion is blocked while an active agent's configuration references the skill. # Delete skill grant Source: https://docs.omnara.com/api-reference/endpoints/skills/delete-skill-grant /api-reference/openapi.yaml delete /orgs/{orgID}/skills/{skillID}/grants/{grantID} Deletes only this project grant. The skill, its revisions, the target project, and other grants are retained, and there are no dependency blockers. # Get skill through ownership authority Source: https://docs.omnara.com/api-reference/endpoints/skills/get-skill-through-ownership-authority /api-reference/openapi.yaml get /orgs/{orgID}/skills/{skillID} Returns one skill with its latest SKILL.md text and file listing. # List skill grants Source: https://docs.omnara.com/api-reference/endpoints/skills/list-skill-grants /api-reference/openapi.yaml get /orgs/{orgID}/skills/{skillID}/grants Lists the projects a skill is shared with, one grant per project, most recently shared first by default. # List skills available to project Source: https://docs.omnara.com/api-reference/endpoints/skills/list-skills-available-to-project /api-reference/openapi.yaml get /orgs/{orgID}/projects/{projectID}/skills Includes directly owned project skills and granted skills, ordered by created_at descending, then id descending. # List skills visible through ownership authority Source: https://docs.omnara.com/api-reference/endpoints/skills/list-skills-visible-through-ownership-authority /api-reference/openapi.yaml get /orgs/{orgID}/skills Items are ordered by created_at descending, then id descending. Without an owner filter, returns the deduplicated union of organization skills the caller may list, project skills the caller may list, and the caller's own user skills. Grants are managed separately and do not change ownership. # Update skill by uploading a new revision Source: https://docs.omnara.com/api-reference/endpoints/skills/update-skill-by-uploading-a-new-revision /api-reference/openapi.yaml post /orgs/{orgID}/skills/{skillID} Creates the skill's next revision from either a replacement archive or replacement SKILL.md content (which keeps every other file from the current revision). The SKILL.md frontmatter `name` must match the skill's existing name. # Accept invitation Source: https://docs.omnara.com/api-reference/endpoints/users-and-api-keys/accept-invitation /api-reference/openapi.yaml post /invitations/{invitationID}/accept # Create org API key Source: https://docs.omnara.com/api-reference/endpoints/users-and-api-keys/create-org-api-key /api-reference/openapi.yaml post /orgs/{orgID}/api-keys Mints an org API key. The plaintext token is returned once and never stored. # Create personal access token Source: https://docs.omnara.com/api-reference/endpoints/users-and-api-keys/create-personal-access-token /api-reference/openapi.yaml post /personal-access-tokens # Decline invitation Source: https://docs.omnara.com/api-reference/endpoints/users-and-api-keys/decline-invitation /api-reference/openapi.yaml post /invitations/{invitationID}/decline # Delete the authenticated user's account Source: https://docs.omnara.com/api-reference/endpoints/users-and-api-keys/delete-the-authenticated-users-account /api-reference/openapi.yaml delete /me Deletes the account, revokes all personal access tokens and browser sessions, removes all organization and project memberships, and releases the account's sign-in identity (emails, linked auth identities, and password credentials) so the same email can register a new account. Personal secrets and skills owned by the user are deleted with the account, and pending invitations they sent are canceled. Deletion is blocked while the user is the last owner of any organization (add another owner or delete the organization first), while a personal skill is referenced by an active agent's configuration, or while a personal secret is referenced by a model provider config, machine pool, or integration. Organization and project resources are unaffected, and agents, machines, and other records the user created are kept with their attribution. # Get an org API key Source: https://docs.omnara.com/api-reference/endpoints/users-and-api-keys/get-an-org-api-key /api-reference/openapi.yaml get /orgs/{orgID}/api-keys/{keyID} # Get the authenticated user and their organizations Source: https://docs.omnara.com/api-reference/endpoints/users-and-api-keys/get-the-authenticated-user-and-their-organizations /api-reference/openapi.yaml get /me # List an org API key's explicit project role grants Source: https://docs.omnara.com/api-reference/endpoints/users-and-api-keys/list-an-org-api-keys-explicit-project-role-grants /api-reference/openapi.yaml get /orgs/{orgID}/api-keys/{keyID}/projects Lists the key's explicit per-project role grants. Keys with the admin org role implicitly have admin access to every project and will not appear here for those projects. # List org API keys Source: https://docs.omnara.com/api-reference/endpoints/users-and-api-keys/list-org-api-keys /api-reference/openapi.yaml get /orgs/{orgID}/api-keys Items are ordered by created_at descending, then id descending. Revoked keys are included. # List pending invitations Source: https://docs.omnara.com/api-reference/endpoints/users-and-api-keys/list-pending-invitations /api-reference/openapi.yaml get /invitations Items are ordered by created_at ascending, then id ascending. # List the authenticated user's personal access tokens Source: https://docs.omnara.com/api-reference/endpoints/users-and-api-keys/list-the-authenticated-users-personal-access-tokens /api-reference/openapi.yaml get /personal-access-tokens Items are ordered by created_at descending, then id descending. # Remove an org API key's project role grant Source: https://docs.omnara.com/api-reference/endpoints/users-and-api-keys/remove-an-org-api-keys-project-role-grant /api-reference/openapi.yaml delete /orgs/{orgID}/api-keys/{keyID}/projects/{projectID} # Revoke a personal access token Source: https://docs.omnara.com/api-reference/endpoints/users-and-api-keys/revoke-a-personal-access-token /api-reference/openapi.yaml post /personal-access-tokens/{tokenID}/revoke # Revoke an org API key Source: https://docs.omnara.com/api-reference/endpoints/users-and-api-keys/revoke-an-org-api-key /api-reference/openapi.yaml post /orgs/{orgID}/api-keys/{keyID}/revoke Terminally revokes the key, removes its org and project roles, and cancels pending invitations it sent. Idempotent. # Set an org API key's role on a project Source: https://docs.omnara.com/api-reference/endpoints/users-and-api-keys/set-an-org-api-keys-role-on-a-project /api-reference/openapi.yaml put /orgs/{orgID}/api-keys/{keyID}/projects/{projectID} Creates or updates the key's explicit role grant on the project. # Update an org API key Source: https://docs.omnara.com/api-reference/endpoints/users-and-api-keys/update-an-org-api-key /api-reference/openapi.yaml patch /orgs/{orgID}/api-keys/{keyID} Renames the key and/or changes its org role. Revoked keys cannot be updated. # Authentication Source: https://docs.omnara.com/api/authentication Personal and organization API keys, device login for CLIs, and token management Two kinds of API keys exist, and both are sent the same way: | Credential | Shape | Acts as | | - | - | - | | Personal API key | `omnara_pat_v1_…` | You — your roles, your project access | | Org API key | `omnara_org_v1_…` | The org itself — a service account for integrations and automations | (Connected machines authenticate with their own daemon tokens, minted in the [connect flow](/machines/connect#install-the-daemon) and valid only on daemon routes.) Treat bearer tokens as opaque: store and send the complete value without parsing its prefix, version, or checksum. ## Using an API key Send either key as a bearer token. This example uses a personal key because `/me` is a user-only endpoint: ```bash theme={null} curl https://api.omnara.com/v1/me \ -H "Authorization: Bearer $OMNARA_TOKEN" ``` ```json theme={null} { "user": { "id": "usr_ehcyd5n6a2bfgik7mv4qtrwz3j", "email": "you@example.com", "display_name": "You" }, "orgs": [ { "id": "org_k7mv4qtrwz3jehcyd5n6a2bfgi", "name": "Acme", "role": "owner", "created_at": "2026-08-01T17:02:11Z" } ] } ``` A personal API key authenticates as you and carries exactly your roles and project access — no more, no less. There are no separate API scopes to configure; what you can do in the dashboard, your token can do through the API. For service integrations, use an [org API key](#org-api-keys-for-services) instead, so access doesn't hinge on one person's account. A missing or invalid token returns `401` with code `unauthorized`. A valid token used on something your roles don't permit returns `403` — or `404` if you can't even see the resource. Details in [Errors](/api/overview#errors). ## Creating personal keys Tokens are minted where a human can approve them — creating one is deliberately *not* possible with another API key: * **Dashboard** — open your account menu at the bottom of the sidebar, choose **Personal tokens**, and click **New token**. The full token is shown once. * **Device login** — for CLIs and dev tools, below. Only a hash is stored server-side, so save the token when it is shown. ## Org API keys for services Services shouldn't run as a person — when that person leaves or their access changes, the integration breaks. An org API key is its own principal: it holds an org role (`admin` or `member`) and per-project roles, granted exactly like a member's. Create one in the dashboard: with **All projects** selected in the project switcher, open **API Tokens** under **Access** (org admins only) and click **New token**. Pick a name and org role; the `omnara_org_v1_…` token is shown once. Then grant the key project roles from its detail panel — a member-role key sees nothing until you do, same as a human member. Key management stays human-gated: creating, renaming, changing roles, and revoking all require a dashboard session, so one API key can never mint or escalate another. Listing and reading key metadata work with regular API auth ([List org API keys](/api-reference/endpoints/users-and-api-keys/list-org-api-keys)). Revocation is permanent, drops the key's org and project roles, and cancels invitations it sent ([Revoke](/api-reference/endpoints/users-and-api-keys/revoke-an-org-api-key)). ## Device login for CLIs Omnara supports the [OAuth 2.0 Device Authorization Grant](https://www.rfc-editor.org/rfc/rfc8628.html). The returned access token is a regular Omnara personal access token. Start by discovering the endpoints for the Omnara installation: ```bash theme={null} curl https://app.omnara.com/.well-known/oauth-authorization-server ``` Use the endpoint URLs returned by this metadata rather than constructing them. **1. Start the flow:** ```bash theme={null} curl https://app.omnara.com/api/auth/device/code \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "client_id=omnara-cli" \ --data-urlencode "token_name=laptop of dana" ``` `client_id` must be `omnara-cli`; other public clients are not currently registered. `token_name` is an optional Omnara extension for naming the resulting PAT; it uses the 64-character resource-name policy and defaults to `Device login`. ```json theme={null} { "device_code": "b62d4cb3…", "user_code": "A1B2C-D3E4F", "verification_uri": "https://app.omnara.com/device", "verification_uri_complete": "https://app.omnara.com/device?user_code=A1B2C-D3E4F", "expires_in": 900, "interval": 5 } ``` **2. Send the user to approve.** Open `verification_uri_complete` (or show `user_code` and point them at `verification_uri`). They sign in and approve — or deny — on the dashboard's **Approve device login** page. **3. Poll for the token** every `interval` seconds with the private `device_code`: ```bash theme={null} curl https://app.omnara.com/api/auth/device/token \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:device_code" \ --data-urlencode "device_code=b62d4cb3…" \ --data-urlencode "client_id=omnara-cli" ``` While the user hasn't decided, the token endpoint returns `400`: ```json theme={null} { "error": "authorization_pending" } ``` Keep polling at the returned interval. After `slow_down`, add five seconds to the interval for this and later requests. On approval, `200` returns the PAT: ```json theme={null} { "access_token": "omnara_pat_v1_…", "token_type": "Bearer" } ``` A denied or expired flow returns `400` with `"access_denied"` or `"expired_token"`. The PAT does not expire automatically; it appears in the approving user's token list, where it can be revoked, and is used as a bearer token for subsequent API calls. ## Connecting MCP clients The Omnara API is also exposed as an MCP server at `https://app.omnara.com/mcp`. MCP clients that support the [MCP authorization spec](https://modelcontextprotocol.io/specification/draft/basic/authorization) discover the OAuth endpoints themselves and sign you in through the browser, so no token needs to be pasted into the client. In Claude, add Omnara from the [connectors directory](https://claude.ai/directory/app-omnara-com). In Claude Code or Codex, add it from the terminal: ```bash Claude Code theme={null} claude mcp add --transport http omnara https://app.omnara.com/mcp ``` ```bash Codex theme={null} codex mcp add omnara --url https://app.omnara.com/mcp ``` Then sign in with `/mcp` in Claude Code or `codex mcp login omnara` in Codex. For other clients, add the server to your client's `mcp.json`: ```json theme={null} { "mcpServers": { "omnara": { "type": "http", "url": "https://app.omnara.com/mcp" } } } ``` On first use the client opens the dashboard's **Authorize access to Omnara** page. Approve it and the client stores its own short-lived tokens, refreshing them in the background. A client you sign in this way acts as you, with your roles in every organization you belong to. To limit a client to specific projects, use a member-role [org API key](#org-api-keys-for-services) with roles on only those projects. Clients without OAuth support can also use a personal access token. Either one goes in the `Authorization` header: ```json theme={null} { "mcpServers": { "omnara": { "type": "http", "url": "https://app.omnara.com/mcp", "headers": { "Authorization": "Bearer omnara_pat_v1_…" } } } } ``` ## Managing tokens The **Personal tokens** page in your account menu — or [List personal access tokens](/api-reference/endpoints/users-and-api-keys/list-the-authenticated-users-personal-access-tokens) — shows your tokens with creation and last-used times, metadata only: the secret is never shown again. `last_used_at` tells you which tokens are actually alive; prune the rest, and [revoke](/api-reference/endpoints/users-and-api-keys/revoke-a-personal-access-token) immediately if a token leaks — revocation is permanent. # API overview Source: https://docs.omnara.com/api/overview How the API is organized, plus the conventions every endpoint shares The Omnara API is JSON over HTTPS. Hosted routes live under: ```text theme={null} https://api.omnara.com/v1 ``` Self-hosted deployments serve the same API from their own origin under `/api/v1`. Every deployment serves its OpenAPI 3.0 specification at `GET /api/openapi.yaml`; the endpoint pages in this reference are generated from that spec. Two ways to read this tab: the generated **Endpoints** pages give you every operation's exact schema and a playground; the [Documentation tab](/introduction) teaches the same operations in task order with worked examples. Each teaching page links to its endpoint pages and vice versa — use whichever mode you're in. ## How the API is organized Most integrations only need the first three groups: launch an agent, respond when it needs a human, and read what it did. | Endpoint group | What it covers | Teaching pages | | - | - | - | | [Agents](/api-reference/endpoints/agents/create-agent) | Launch agents, send inputs, manage the backlog, custom tool calls | [Agents](/agents/overview), [Sending input](/events/sending-input) | | [Interactions](/api-reference/endpoints/interactions/list-agent-interactions) | The approvals and questions that pause an agent | [Approvals & questions](/events/interactions) | | [Events](/api-reference/endpoints/events/list-events) | Read or stream the timeline, list turns, download artifacts | [Streaming events](/events/streaming), [Artifacts](/events/artifacts) | | [Configs and Profiles](/api-reference/endpoints/configs-and-profiles/create-agent-config) | Create agent configs, manage launch profiles | [Configs & profiles](/agents/configuration) | | [Models](/api-reference/endpoints/models/create-model-provider-config) | Model providers, configured models, project model access | [Model providers](/organization/model-providers) | | [Machines](/api-reference/endpoints/machines/create-machine) | Machines, project machine access, daemon tokens | [Connect a machine](/machines/connect) | | [Machine Pools](/api-reference/endpoints/machine-pools/create-machine-pool) | Machine pools and project pool access | [Machine pools](/machines/pools) | | [Secrets](/api-reference/endpoints/secrets/create-secret) | Org-, project-, and user-owned secrets, availability and grants | [Secrets](/organization/secrets) | | [Skills](/api-reference/endpoints/skills/create-skill) | Skill packages, revisions, project skill access | [Skills](/tools/skills) | | [Organizations and Projects](/api-reference/endpoints/organizations-and-projects/create-organization) | Organizations, membership, invitations, projects | [Members & access](/organization/members) | | [Users and API Keys](/api-reference/endpoints/users-and-api-keys/get-the-authenticated-user-and-their-organizations) | The authenticated user, tokens, pending invitations | [Authentication](/api/authentication) | | [Actors](/api-reference/endpoints/actors/list-project-actors) | External identity attribution for inputs and resolutions | [Sending input](/events/sending-input#who-said-that-actors-and-attribution) | A typical agent workflow maps onto the groups like this: 1. **Define** — create a config with `POST .../agent-configs` ([Agent configuration](/agents/configuration)) 2. **Launch** — `POST .../agents` with a config or profile ([Agents](/agents/overview)) 3. **Follow** — `GET .../events/stream` for the live timeline ([Streaming events](/events/streaming)) 4. **Respond** — resolve approvals and questions as they appear ([Approvals & questions](/events/interactions)) 5. **Continue** — send follow-ups with `POST .../inputs` ([Sending input](/events/sending-input)) ## Authentication Create an API key in the dashboard and send it as a bearer token: ```bash theme={null} curl https://api.omnara.com/v1/me \ -H "Authorization: Bearer $OMNARA_TOKEN" ``` Personal API keys act as the user who created them, with that user's roles. Org API keys are separate principals with their own org and project roles. [Authentication](/api/authentication) covers both, plus device login and token management. ## Public IDs Every resource has a stable public ID with a type prefix (`agt_`, `acfg_`, `evt_`, …) followed by 26 lowercase base32 characters. IDs are opaque — don't parse anything but the prefix. ## Pagination | Style | Endpoints | Request parameters | Response fields | | - | - | - | - | | Cursor | Most list endpoints | `cursor`, `limit` | `data`, `next_cursor` | | Sequence | Agent events and turns | `after_sequence`, `before_sequence`, `before_turn_sequence` | `next_after_sequence`, `next_before_*`, `has_more` | Pass the returned cursor back on the next request; a `null` cursor means you've reached the end. Sequence pagination is explained with examples in [Streaming events](/events/streaming#read-the-timeline). ## Idempotency Create endpoints — orgs, projects, profiles, machines, and machine/pool grants — accept an optional `Idempotency-Key` header (the API reference marks each one). Retrying with the same key and an identical body returns the original resource instead of creating a duplicate — a replay returns `200` where the first request returned `201`; a conflicting retry returns `409` with code `idempotency_key_conflict`. Agent input creation and config changes use the key alone: a retry returns the original stored result without comparing the request body. Organization creation also accepts this header but does not require it. A key determines the proposed organization ID, so a failed retry uses the same ID. Once the organization commits, reusing the key returns it without another hosted call. ## Errors Every error, from every endpoint, is the same two-field envelope: ```json theme={null} { "error": "agent config source does not match JSON schema: /model: required", "code": "validation_failed" } ``` Branch on `code` — it's a stable contract, enumerated in the OpenAPI spec, and each endpoint page in the reference lists the errors it can return. Never match on `error`; it's for humans and may change without notice. Two rules worth internalizing: * **Resources you cannot read return `404`, not `403`.** The API doesn't confirm the existence of things outside your access. A `403` always means "the resource is visible to you, but your role doesn't permit this action." * **Retry `424`, `429` and `5xx` with backoff, reusing the same `Idempotency-Key`** so a retry can never act twice. `424` with code `upstream_unavailable` means a server you configured, such as an MCP server, did not respond or failed; retries succeed once that server recovers. A `409` from MCP tool discovery means another refresh of the same server or auth secret is in progress; retry it shortly. A `409` with code `agent_backlog_full` means the agent has 50 pending messages; wait for processing or cancel a pending message before retrying. Other `4xx` errors won't change on retry — fix the request instead. ## Rate limits Requests count against two allowances per user, or per org for org API keys: 100,000 reads (`GET`) and 50,000 other requests per hour, refilling continuously, with bursts of up to 1,000. Over a limit, requests fail with `429`, code `rate_limited`, and a `Retry-After` header; MCP tool calls return a tool error with the same code. Use the [event stream](/events/streaming) or an [event webhook](/agents/configuration#event-webhook) instead of polling. Self-hosted deployments can [change these limits](/self-hosting/configuration#api-rate-limits). ## Request size limits Most endpoints cap request bodies at 1 MiB. Agent inputs and custom tool call results accept up to 48 MiB for base64-encoded media. Skill upload requests allow 26 MiB of body data to carry an archive of up to 25 MiB. Decoded media has stricter caps — see [Attach files and images](/events/sending-input#attach-files-and-images). Oversized requests fail with `413` and code `request_too_large`. ## Streaming Each agent exposes its timeline as a Server-Sent Events stream: ```text theme={null} GET /orgs/{orgID}/projects/{projectID}/agents/{agentID}/events/stream ``` Use `after_sequence` or `Last-Event-ID` to resume after a disconnect. [Streaming events](/events/streaming) covers the frame types, delta previews, and resume semantics. # Changelog Source: https://docs.omnara.com/changelog New features, improvements, and fixes in Omnara **October 29, 2026** ## E2B sandboxes, scoped Git access, and four new example agents E2B and Superserve join the sandbox providers, Git credentials can be limited to the repositories you choose, and four new example agents are ready to deploy. ### New example agents * **Email agent**: gets its own email address through Primitive, and each email thread becomes its own conversation. * **Browser agent**: signs in to web apps with credentials stored as secrets and works in a real browser for hours. * **PR review agent**: reviews pull requests through the GitHub integration, with no GitHub token needed. * **SEO audit agent**: audits a page for SEO, AEO, and GEO and delivers a graded PDF report. See [Examples](/examples/overview). ### E2B and Superserve sandboxes Bring your E2B or Superserve account as a sandbox provider. Machines start from a template you choose, and Superserve's default `superserve/omnara` template includes Omnara's default machine environment. The pool form now checks your credentials automatically before you save. See [E2B](/machines/providers/e2b) and [Superserve](/machines/providers/superserve). ### Scoped Git access Choose an agent's Git credentials in the builder, limit them to specific repositories, or make them read-only. Credentials can push only when the integration has **Let agents push code** turned on, which also needs Contents: Read & write on the GitHub App. See [Git credentials](/integrations/github#git-credentials). ### Launch with any config Pass a `config` or `config_source` when launching an agent, with or without a profile, and write configs in JSON as well as YAML. Agents launched from Slack, Discord, or GitHub can now have their configs edited like any other agent. See [Launch an agent](/agents/overview#launch-an-agent). ### Webhooks for a project, profile, or agent Event webhooks are now managed on their own instead of inside agent configs. Attach one to a project, to a profile (covering its current and future agents), or to a single agent. Manage profile and agent webhooks from the dashboard, and project webhooks through the API. See [Event webhooks](/events/webhooks). ### Dashboard updates New onboarding starts from a prompt describing the agent you want and shows a diagram of the system it builds. The Models, Machines, and Skills pages are redesigned, and a new org-wide Memory page lists stores across projects. ### Agent improvements * Agents can wait up to five minutes for process output in one call instead of ten seconds, so long jobs need far fewer polls. Connected machines need `omnarad` 0.2.6 or later. * Long conversations compact based on the provider's token counts, and agents recover when a provider reports the context is too long. * Omnara-managed machines now run Ubuntu 26.04 with Python 3.14, PostgreSQL 18, and Redis 8. Start PostgreSQL with `pg_createcluster 18 main --start`. See [Default machine environment](/machines/pools#default-machine-environment). ### Fixes and notes * API requests are rate limited per account, at 100,000 reads and 50,000 writes per hour. Requests over the limit return `429` with `Retry-After`. See [Rate limits](/api/overview#rate-limits). * `read_process` pages output by byte offset, and list tools use `offset` and `limit` instead of cursors. * `GET /agents/{agentID}` returns the agent's activity, tool-call events include interaction updates, and org API keys can grant models to projects. * Model requests to OpenAI, OpenRouter, and Anthropic include a hashed per-org safety identifier. Self-hosted deployments send one only when `OMNARA_SAFETY_IDENTIFIER_KEY` is set. * NUL bytes in command output no longer hang tool calls, tool results are no longer duplicated when sent to the model, machine errors tell agents what to do next, reasoning effort works for models on custom providers, and a bot mentioned before its integration is set up now replies. **October 7, 2026** ## Integrations, memory stores, and a redesigned dashboard This is our biggest update since launch. Omnara now connects to Slack, Discord, and GitHub, agents can keep files in shared memory stores, and the dashboard has been redesigned. Examples are now agents you deploy with your coding agent, and four more sandbox providers are available. ### Slack, Discord, and GitHub integrations Create an integration from a project's Integrations page, connect your account, and choose which agent profiles it can launch. In Slack and Discord, mention the bot to start an agent in a thread, or message it directly in Slack. The agent replies in the thread, can ask questions or request approvals there, and schedules can start new threads on a timer. On GitHub, an agent can launch when a pull request opens or someone mentions it. It then receives that PR's comments, reviews, and commits, can read the diff, comment, and submit reviews, and can use Git over HTTPS with your GitHub App's access. See [Integrations](/integrations/overview). ### Memory stores Create a memory store in a project and attach it to agents with read or read-write access. Agents read and write its files at `/memory//` with the file tools, so they can keep notes between conversations or share files with other agents. Manage stores and files from the new Memory page, the API, or `omnara memory-stores`. See [Memory stores](/agents/configuration#memory-stores). ### Redesigned dashboard A new sidebar that adapts to where you are, redesigned agent and profile pages, an improved overview, and simpler dialogs for model providers, models, and machine pools. ### Deploy example agents with your coding agent Each example is an `agent.yaml` and a `SKILL.md`. Ask your coding agent to read the skill and it sets the agent up with the `omnara` CLI: signing in, storing secrets, picking a model, and doing a first run. New examples include an SRE agent that investigates production through AWS, a coding agent you mention in Slack that opens pull requests, and a video agent that renders MP4s with Remotion. See [Examples](/examples/overview). ### More sandbox providers Tenki, Arker, boxd, and CreateOS join as sandbox providers you can bring your own account for. Each provider now has its own setup and configuration page, plus an overview comparing them. See [Sandbox providers](/machines/providers/overview). ### Fixes and notes * Agents started from Slack before this release can still post in their threads, but new replies there no longer reach them. Mention the bot to start a new agent. * On machines you connected yourself, upgrade `omnarad` to 0.1.36 or later to use Git credentials from the GitHub integration. * Self-hosted deployments need `OMNARA_MEMORY_DIR` on a filesystem shared by the API and every worker. See [Memory storage](/self-hosting/configuration#memory-storage). * Blaxel machines that take longer to start from a cold image no longer fail. * `web_fetch` keeps code blocks from fetched pages. * The X, Reddit, LinkedIn, and PostHog examples no longer include `demo.ts` scripts, and `posthog-pulse-agent` is now `posthog-analytics-agent`. **September 30, 2026** ## GPT-6, Claude Opus 5.5, and more tools on hosted machines The hosted catalog now includes GPT-6 Sol and Luna and Claude Opus 5.5, and machines in Omnara-managed pools come with common development tools preinstalled. Also new: Freestyle as a sandbox provider, easier skill creation, image viewing for agents, and a reasoning effort selector. ### New models `omnara-openrouter` now includes GPT-6 Sol, GPT-6 Luna, and Claude Opus 5.5, and new agents default to `openai/gpt-6-sol`. GPT-5.6 Sol, GPT-5.6 Luna, and Claude Opus 5 are being retired: agents already using them keep working, and each model is removed once nothing references it. Update saved configs and scripts that name them. ### More tools on Omnara-managed machines New machines in Omnara-managed pools, including `default-pool`, now run Ubuntu 24.04 with common development tools preinstalled: Python, Node.js, Go, uv, pnpm, PostgreSQL, Redis, `gh`, Python data and document libraries, and Playwright with headless Chromium. Agents can start working without installing them first. Existing machines keep their current image. See [Default machine environment](/machines/pools#default-machine-environment). ### Freestyle sandboxes Bring your Freestyle account as a sandbox provider alongside Blaxel, Unikraft Cloud, Daytona, and Modal. See [Machine pools](/machines/pools). ### Create skills faster Drop a folder, archive, or `SKILL.md` into the Create skill dialog, or write one in the built-in editor. Upload several skills at once and review which are new, which are updated, and which are skipped before anything uploads. Skill uploads from the SDK, CLI, and API now validate correctly. See [Skills](/tools/skills). ### Agents can see images `read_file` returns PNG, JPEG, GIF, and WebP files to the model as images, so agents can look at screenshots and charts they save from a sandbox. See [Built-in tools](/tools/built-in). ### Reasoning effort and account controls Pick a reasoning effort next to the model in the agent builder for models that support it. You can also delete your account or an organization from the dashboard. ### Fixes * MCP errors now return useful status codes instead of generic server errors. * The example agents work with the current SDK. **September 24, 2026** ## Agents can now send events to your webhooks Agents can POST their events to your own endpoint as they work. Also new: a redesigned org overview, per-call machine sizing, and custom headers for model providers. ### Event webhooks Add `event_webhook` to an agent's config with an HTTPS URL and the event types you want, such as tool-call updates or model output. Omnara sends each event as it happens and retries failed deliveries for up to ten minutes. Sign requests with a secret to verify they came from Omnara, and validate payloads with the SDK's schema. See [Event webhooks](/agents/configuration#event-webhooks) and the [webhook guide](/events/webhooks). ### Org overview The organization home page now shows today's activity and a 30-day summary of model usage, along with your recent agents and profiles. ### Size machines per call When an agent creates a machine, it can request CPU and memory for that machine, where the provider supports it, within your pool and project limits. The requested size appears in the approval prompt. Agents now find pools through `list_machines` and select them by ID, so renaming a pool can't redirect a request. ### Custom headers for model providers Model providers your organization manages can send extra headers with every request, either as plain values or from secrets. This is useful for API gateways and proxies that need their own authentication or routing headers. See [Model providers](/organization/model-providers). ### Fixes and notes * Deleting a secret that an agent's environment references no longer stops new processes or machines from starting; the variable is left out instead. Provider headers that reference a deleted secret are left out the same way. * The compiled config now appears under the YAML editor for agents, profiles, and subagents, showing the tools Omnara adds automatically. * Archiving a subagent no longer sends a message to its parent. * Browser translation extensions no longer crash the dashboard. * Self-hosted Docker Compose deployments now bundle RustFS instead of MinIO for artifact storage. See [Configuration](/self-hosting/configuration). **September 18, 2026** ## Agents can now delegate to subagents Declare subagents in a config and an agent can spin up helpers, hand each one a task, and collect their results in a single conversation. Also new: deferred tool loading, usage and cost pages, large tool results as files, and custom model endpoints in the dashboard. ### Subagents Add a `subagents` block to an agent's config and it gains tools to spawn helpers, send them messages, read their progress, and stop them. A subagent launches from another profile or from a copy of the parent's own config, starts with a clean context, and shares the parent's machines. Its final answer arrives back in the parent's conversation. You set how many can run at once and how deep they can nest, and they appear in the dashboard and API as agents with a parent. See [Subagents](/agents/configuration#subagents). ### Deferred tools and tool search Mark any tool `deferred: true` to keep its definition out of the model's context until it's needed. The model calls `tool_search` to load matching tools on demand, which keeps prompts small and the provider's prompt cache intact for agents with large MCP tool sets. See [Deferred tools](/agents/configuration#deferred-tools). ### Usage and cost New usage pages for your organization, projects, profiles, and agents show model API usage, and the model catalog now lists pricing per million input and output tokens. ### Large results become files Tool results over 50 KiB are saved as files instead of filling the conversation. The agent gets a preview and can page through or search the full result with the new `read_file` and `search_files` tools, with no machine required. The transfer tools are now `upload_file` and `download_file` and work with paths under `/artifacts`; existing configs were migrated automatically. See [Built-in tools](/tools/built-in). ### Dashboard Add a model provider with a custom base URL and API format (OpenAI Responses, OpenAI Chat Completions, or Anthropic Messages) from the Add provider dialog, for proxies and self-hosted servers such as LiteLLM, vLLM, or Ollama. Open an agent's raw event log from its conversation, and get a warning before leaving unsaved agent or profile edits. ### Reliability and API notes Daemon crashes on connected machines now back off instead of restarting in a tight loop, and failure reports capture the crash output. Dependency updates resolved all open high-severity advisories. For API users: machine tools and bindings now use public machine IDs (`mch_...`) and the per-agent `machine_ref` field was removed from responses. **September 14, 2026** ## Omnara is now available over MCP MCP joins the API, SDK, and CLI as a way to work with Omnara. Connect Claude Code, Cursor, or any MCP client to the Omnara API and spin up Omnara agents from another agent. Also new: Modal sandboxes, Amazon Bedrock with AWS credentials, and in-place secret editing. ### Connect an MCP client Add `https://app.omnara.com/mcp` to any MCP client. For example, with Claude Code: ```bash Claude Code theme={null} claude mcp add --transport http omnara https://app.omnara.com/mcp ``` Or with Codex: ```bash Codex theme={null} codex mcp add omnara --url https://app.omnara.com/mcp ``` Sign in through the browser on first use; there are no tokens to paste. Your agent can then launch Omnara agents, send them messages, follow their progress, and manage profiles, secrets, and machines, with the same permissions you have in the dashboard. Omnara is also listed in the official MCP Registry as `com.omnara/omnara`. See [Connecting MCP clients](/api/authentication#connecting-mcp-clients). ### Modal sandboxes Bring your Modal account as a sandbox provider alongside Blaxel, Unikraft Cloud, and Daytona. Create a pool with your Modal token and choose the image, region, and resource limits. See [Machine pools](/machines/pools). ### Amazon Bedrock with AWS credentials Bedrock model providers can authenticate with AWS credentials instead of an API key, including assuming a role. They use the same AWS credential secrets as SigV4 MCP servers. See [Model providers](/organization/model-providers). ### Edit secrets in place Rename a secret or replace its value from the dashboard. Agent configs that reference it keep working. See [Secrets](/organization/secrets). ### Reliability Agents that reach a model's output limit now continue their answer instead of stopping short. You can set idle and total timeouts for provider requests. Commands waiting on a machine that looks online but isn't responding are released after five minutes so the agent can move on. **September 9, 2026** ## Introducing Omnara On September 2, we relaunched Omnara as the open-source alternative to Claude Managed Agents. Run agents for yourself or your team, or build them into your product through the API. Omnara handles durable execution, sandboxes, and conversation history while you choose how each agent works. Here’s what we’ve shipped so far. [Introducing Omnara](https://www.youtube.com/embed/6i3ScDe7gcs) ### Agents that pick up where they left off Agents keep their conversation history across crashes, restarts, and machine disconnects, so work can resume without starting a new conversation. Set one up in the dashboard or with a small [configuration file](/agents/configuration). Save that setup as a profile to reuse it whenever you need a new agent. ### Choose your models and machines Use models from OpenAI, Anthropic, OpenRouter, or Amazon Bedrock, or connect a compatible model you host yourself. You can choose the [model](/organization/model-providers) that fits each agent's work. When an agent needs to run commands, it can use an Omnara-managed sandbox or a [machine you connect](/machines/overview), such as a laptop or server. You can also bring your own Blaxel, Unikraft Cloud, or Daytona account. Agents can create sandboxes when they need them and release them when the work is done. ### Connect tools and share skills Agents can search the web, run commands, and work with files using built-in tools. Connect [MCP servers](/tools/mcp) to give them access to your services and data, or add custom tools for actions in your own application. You can browse available MCP servers and their tools in the dashboard. [Skills](/tools/skills) let you package instructions and supporting files for work your agents do regularly. Upload and edit them in the dashboard, then reuse them across agents and projects. ### Work with agents as they run Follow an agent's progress live, send a correction while it's working, or queue up its next task. You can decide which [actions need approval](/tools/permissions), and agents can pause to ask questions when they need your input. You can also attach images and documents to a conversation. Agents can return reports, spreadsheets, and other files for you to view or download. ### Slack and scheduled work Connect an agent profile to [Slack](/integrations/slack) so your team can work with it in threads and DMs. Each conversation keeps its own history, and people can answer questions, approve actions, and receive files directly in Slack. For recurring work, schedules can start a new agent or send a task to an existing one. We've included [examples](/examples/overview) that collect updates from X, Reddit, LinkedIn, and PostHog and deliver a digest to Slack. ### Build agents into your product The [REST API](/api/overview), TypeScript SDK, and CLI let you launch agents, send messages, follow their progress, and respond to requests for input. You can build these interactions into your own application or work with agents from your terminal. For teams, projects and roles control who can manage or use agents and shared resources. Store credentials as secrets and choose which projects can access your models, machines, and skills. Get started on [Omnara Cloud](https://app.omnara.com) or [self-host](/self-hosting/deployment) on your own infrastructure. Omnara is open source under Apache 2.0. The [quickstart](/quickstart) walks through launching your first agent. # Concepts Source: https://docs.omnara.com/concepts Key concepts to understand when using Omnara Omnara provides a stateful API for interacting with agents. This page provides an overview of the core entities you should understand when interacting with the dashboard or API. ## Organizations and Projects When you join Omnara, you join or create an **organization**. Organizations manage [memberships](/organization/members), own shared team resources, and contain **projects**. Projects contain [agents](/agents/overview), their [configs and profiles](/agents/configuration), and the grants that control which shared resources those agents may use. ## Resources Shared resources are owned at the org, project, or user scope: * **[Machines and machine pools](/machines/overview)** (org owned): existing computers you [connect](/machines/connect) (BYO) or [pools](/machines/pools) of ephemeral sandboxes that agents run commands on. * **[Model providers and models](/organization/model-providers)** (org owned): the model endpoints and configured models agents can use. * **[Skills](/tools/skills)** (org, project, or user owned): reusable instructions and tools for agents. * **[Secrets](/organization/secrets)** (org, project, or user owned): credentials referenced by model providers, machine and their environments, and [MCP servers](/tools/mcp). ## Grants A project can use a resource only if it owns it or holds an explicit **grant**. Org and user ownership alone don't make a resource visible to a project — granting is the deliberate step that shares a model, pool, machine, secret, or skill with a project. Own each resource at the narrowest scope that needs it, and grant outward. In the web app, this step is called **sharing**: sharing a resource with a project creates a grant, which is the term the API and CLI use. ## Agents [Agents](/agents/overview) are the core feature of Omnara. Define an agent's model, [tools](/tools/permissions), and instructions in a [config or profile](/agents/configuration). Then Omnara runs the agent loop and runtime. Once an agent has started, interact with it by: * Reading or [streaming](/events/streaming) it's event log * [Sending inputs](/events/sending-input), such as messages or file attachments * Resolving [questions and approvals](/events/interactions) ## Integrations [Integrations](/integrations/overview) connect one project to a provider such as Slack, Discord or GitHub. Create an integration from the project's Integrations page, configure its credentials, and optionally choose which profiles it launches from provider events. An integration can supply tools for taking actions, a subscription capability for receiving activity, and an interaction handler for presenting questions and approvals. Agent configs select tools and handlers; launch requests or the integration subscriptions API attach conversations to agents. Changing config or removing a sending tool does not change subscriptions. Stop incoming forwarding from the integration's **Conversations** list or delete the subscription through the API. ## Continue > [card] [Quickstart](/quickstart) > > Write a config, create the grants, and launch an agent with the dashboard, the API, or the CLI > [card] [API reference](/api/overview) > > Conventions, authentication, errors, and every endpoint # Artifacts Source: https://docs.omnara.com/events/artifacts View metadata and download files referenced in an agent's event log The event log stays lightweight because it never embeds file bytes. When inline media enters the conversation, the bytes are stored as an **artifact** (`art_…`) and the persisted event carries a `media_ref` block pointing at it: ```json theme={null} { "type": "media_ref", "artifact_id": "art_z3jehcyd5n6a2bfgik7mv4qtrw" } ``` In the dashboard, artifacts appear as downloadable cards or attachment chips on the conversation timeline; rendering your own timeline means resolving each `media_ref` with the two endpoints on this page. Model context identifies conversation attachments by their public `artifact_id`, allowing an agent with [`storage_download`](/tools/built-in#storage_download) to copy one to an attached machine using `path: "/artifacts/"` and a machine `destination`. ## Artifacts uploaded by an agent A successful [`storage_upload`](/tools/built-in#storage_upload) result contains the `path` as structured data and a `media_ref` for presentation. Use the artifact ID from the final component of the path with the metadata and content endpoints below. The relevant fields in the resulting event look like this: ```json theme={null} { "event_kind": "tool_result", "tool_call_id": "tcl_wz3jehcyd5n6a2bfgik7mv4qtr", "outcome": "succeeded", "content_blocks": [ { "type": "structured_data", "value": { "path": "/artifacts/art_z3jehcyd5n6a2bfgik7mv4qtrw" } }, { "type": "media_ref", "artifact_id": "art_z3jehcyd5n6a2bfgik7mv4qtrw", "exclude_from_model_context": true } ] } ``` ```bash theme={null} export BASE="$OMNARA_API/orgs/$ORG/projects/$PROJ" export AGENT="agt_v4qtrwz3jehcyd5n6a2bfgik7m" ``` ## Get artifact metadata Fetch the descriptor first when you need to decide how to render or whether to download: ```bash theme={null} curl "$BASE/agents/$AGENT/artifacts/art_z3jehcyd5n6a2bfgik7mv4qtrw" \ -H "Authorization: Bearer $OMNARA_TOKEN" ``` ```json theme={null} { "id": "art_z3jehcyd5n6a2bfgik7mv4qtrw", "org_id": "org_k7mv4qtrwz3jehcyd5n6a2bfgi", "project_id": "proj_7mv4qtrwz3jehcyd5n6a2bfgik", "agent_id": "agt_v4qtrwz3jehcyd5n6a2bfgik7m", "content_type": "application/pdf", "filename": "eu-ai-act-summary.pdf", "digest": "sha256:9f2ab8…", "size_bytes": 482133, "created_at": "2026-08-03T19:15:40Z" } ``` `digest` is a content hash — useful for caching and deduplication on your side: if you've already stored that digest, skip the download. > [!NOTE] > > Full schema and playground: [Get artifact](/api-reference/endpoints/events/get-artifact). ## Download the content The content endpoint returns the raw bytes with the artifact's content type: ```bash theme={null} curl -o eu-ai-act-summary.pdf \ "$BASE/agents/$AGENT/artifacts/art_z3jehcyd5n6a2bfgik7mv4qtrw/content" \ -H "Authorization: Bearer $OMNARA_TOKEN" ``` Content is immutable — the same artifact ID always yields the same bytes, so responses are safe to cache indefinitely. > [!NOTE] > > Full schema and playground: [Get artifact content](/api-reference/endpoints/events/get-artifact-content). ## Where artifacts come from | Source | What happens | | - | - | | Inline `media` block on an [input](/events/sending-input#attach-files-and-images) | Persisted as an artifact; the stored event shows a `media_ref`. | | Inline `media` block on a [custom tool result](/api-reference/endpoints/agents/submit-a-result-for-a-ready-custom-tool-call) | Same conversion — submission accepts bytes, the timeline stores a reference. | | `storage_upload` | The durable tool result stores the path and a `media_ref`. | | Model output with media | The `model_output` event references it. | There's no general-purpose public endpoint for creating artifacts; they're created during agent interactions. ## Next > [card] [Streaming events](/events/streaming) > > Where media\_ref blocks appear in each event kind > [card] [Sending input](/events/sending-input) > > Attach files to your messages # Approvals & questions Source: https://docs.omnara.com/events/interactions Respond when an agent needs approval or an answer Interactions pause an agent when it needs a person to approve an action or answer a question. The agent resumes when someone responds; if the current work is canceled, the interaction is canceled too. There are two kinds: | `interaction_kind` | Created when | Typical form | | - | - | - | | `permission` | A tool with `always_ask` [permission](/tools/permissions) is called | "Permission requested for run\_command" with Allow/Deny | | `question` | The agent uses the `ask_question` built-in tool | Whatever the agent wants to know, with options it composed | Both ride on the same tool-call machinery: the parent tool call waits while the interaction is `open`. An interaction ends in one of two states — `resolved` (someone answered) or `canceled` (the work it guarded was canceled, superseded, or archived). ```bash theme={null} export BASE="$OMNARA_API/orgs/$ORG/projects/$PROJ" export AGENT="agt_v4qtrwz3jehcyd5n6a2bfgik7m" ``` ## Find open interactions > [tab] API > > The examples assume the client, `$ORG`/`orgID`, and `$PROJ`/`projectID` setup from the [quickstart](/quickstart), plus the `$AGENT`/`agentID` value above. The CLI has no interactions commands — respond from the dashboard or the API. > > ```bash REST theme={null} > curl "$BASE/agents/$AGENT/interactions?state=open" \ > -H "Authorization: Bearer $OMNARA_TOKEN" > ``` > > ```typescript SDK theme={null} > const interactions = await sdk.listAgentInteractions({ > client, > path: { orgID, projectID, agentID }, > query: { state: 'open' }, > }) > console.log(interactions.data) > ``` > > ```json theme={null} > { > "data": [ > { > "id": "int_rwz3jehcyd5n6a2bfgik7mv4qt", > "org_id": "org_k7mv4qtrwz3jehcyd5n6a2bfgi", > "project_id": "proj_7mv4qtrwz3jehcyd5n6a2bfgik", > "agent_id": "agt_v4qtrwz3jehcyd5n6a2bfgik7m", > "interaction_kind": "permission", > "state": "open", > "request": { > "title": "Permission requested for run_command", > "context": [ > { "label": "Command", "value": "rm -rf ./build && npm run build" }, > { "label": "Machine", "value": "mch_aaaaaaaaaaaaaaaaaaaaaaaaae" }, > { "label": "Shell", "value": "bash" } > ], > "questions": [ > { > "prompt": "Allow this tool call?", > "options": [ > { "label": "Allow" }, > { "label": "Deny", "allows_text": true } > ] > } > ] > }, > "created_at": "2026-08-03T19:13:20Z" > } > ], > "next_cursor": null > } > ``` > > Filter by `state` (`open`, `resolved`, `canceled`) or omit it for everything; results are cursor-paginated, oldest first. Open interactions announce themselves through `tool_call_update` frames on the [event stream](/events/streaming) and [configured webhooks](/events/webhooks); use this endpoint to fetch the form to render. > > > [!NOTE] > > > > Full schema and playground: [List agent interactions](/api-reference/endpoints/interactions/list-agent-interactions). > [tab] Dashboard > > Open interactions appear as cards at the bottom of the agent's conversation. A permission card is titled **Permission requested for** followed by the tool name; a question card is titled with the agent's own wording. Each shows the form's context and options ready to answer. Nothing to poll or refresh — they show up live. ## Render the form Every interaction's `request` is the same small form structure, designed to render without special-casing: * `title` — one line saying what's being asked. * `context` — optional label/value pairs to display verbatim (the command about to run, the machine it targets). Show all of them; they're the approver's evidence. * `questions` — one or more, in order. Each has a `prompt` and at least one option. `multiple: true` means the answer may select several options; an option with `allows_text: true` invites free text alongside the selection (in permission forms, that's the "Deny" option — the text becomes the reason the model sees). Permission forms always have exactly one question with `Allow` at index 0 and `Deny` at index 1. Question forms are composed by the agent, so treat them generically. ## Resolve it > [tab] API > > Answer with one entry per question, in order. `option_indices` are zero-based positions into that question's `options`. The response is the interaction in its final state: > > ```bash REST theme={null} > curl "$BASE/agents/$AGENT/interactions/int_rwz3jehcyd5n6a2bfgik7mv4qt/resolve" \ > -H "Authorization: Bearer $OMNARA_TOKEN" \ > -H "Content-Type: application/json" \ > -d '{ "answers": [ { "option_indices": [0] } ] }' > ``` > > ```typescript SDK theme={null} > const resolved = await sdk.resolveAgentInteraction({ > client, > path: { orgID, projectID, agentID, interactionID: 'int_rwz3jehcyd5n6a2bfgik7mv4qt' }, > body: { answers: [{ option_indices: [0] }] }, > }) > console.log(resolved.data) > ``` > > ```json theme={null} > { > "id": "int_rwz3jehcyd5n6a2bfgik7mv4qt", > "interaction_kind": "permission", > "state": "resolved", > "request": { "title": "Permission requested for run_command", "...": "..." }, > "resolution": { "answers": [ { "option_indices": [0] } ] }, > "resolved_by_input_id": "ain_fgik7mv4qtrwz3jehcyd5n6a2b", > "resolved_at": "2026-08-03T19:14:01Z", > "...": "..." > } > ``` > > To deny with a reason, select the Deny option and attach text: > > ```bash REST theme={null} > curl "$BASE/agents/$AGENT/interactions/int_rwz3jehcyd5n6a2bfgik7mv4qt/resolve" \ > -H "Authorization: Bearer $OMNARA_TOKEN" \ > -H "Content-Type: application/json" \ > -d '{ "answers": [ { "option_indices": [1], "text": "Not on the build server — use a pool machine." } ] }' > ``` > > ```typescript SDK theme={null} > await sdk.resolveAgentInteraction({ > client, > path: { orgID, projectID, agentID, interactionID: 'int_rwz3jehcyd5n6a2bfgik7mv4qt' }, > body: { > answers: [{ option_indices: [1], text: 'Not on the build server — use a pool machine.' }], > }, > }) > ``` > [tab] Dashboard > > A permission card answers in one click: click **Allow** or **Deny** and the agent resumes immediately. To explain a denial, click **Add a reason** first, type in the **Reason for denying (optional)** field, then click **Deny** or press Enter; the model sees that text as the reason. > > On a question, click the agent's options. Where a selected option invites free text, a **Details (optional)** field appears next to the options. Click **Submit**, or press Enter in the text field, and the agent resumes immediately. Two operational notes: * **Resolution is first-writer-wins.** Re-sending the *same* answers replays idempotently — `200` with the already-resolved interaction. Sending *different* answers, or resolving a canceled interaction, returns `409` with code `idempotency_key_conflict`. Whoever answers first, wins; build your approval UI to handle the race gracefully. * **Attribution works like inputs.** Services relaying human decisions pass `actor` alongside `answers` so `resolved_by_input_id` traces to the real person — see [actors](/events/sending-input#who-said-that-actors-and-attribution). > [!NOTE] > > Full schema and playground: [Resolve agent interaction](/api-reference/endpoints/interactions/resolve-agent-interaction). ## When interactions cancel You never delete an interaction; it cancels when the work it guarded goes away: * Sending input with [`cancel_open_interactions: true`](/events/sending-input#queued-vs-steering) — your new message supersedes the pending prompts; their tool calls complete as `canceled`. * [Stopping the agent](/agents/overview#stop-current-work) or [archiving it](/agents/overview#archive-an-agent) — everything in flight, interactions included, is canceled. Canceled interactions keep their `request` and stay listable, so audit trails show what was asked even when nobody answered. ## Present interactions in your own app Your application decides where to display questions and approvals. Read the interaction's `request`, present the form, and resolve it through the ordinary project-authorized API. The dashboard remains available if delivery fails. Reconcile the interaction list on reconnect and every few minutes while connected; stream hints alone are insufficient. For an interaction routed to an integration handler, list and resolve responses include its captured `destination`. Its `integration_id` and `integration_target_id` are public IDs; `handler_key` identifies the handler and `conversation` contains the provider fields, using the same format as integration subscriptions. This snapshot stays fixed when the agent's current destination changes. Optional `presentation_receipt` contains recorded provider message metadata. These fields describe integration delivery; your application can present interactions without them. See [Integrations](/integrations/overview) for handler configuration and integration-specific behavior, or [Custom integrations](/integrations/custom-integrations#present-and-resolve-interactions) for presenting interactions through your own service. ## Next > [card] [Artifacts](/events/artifacts) > > Download the files agents produce and reference > [card] [Agent configuration](/agents/configuration) > > Decide which tools require approval in the first place # Sending input Source: https://docs.omnara.com/events/sending-input Deliver messages to a running agent Every message or control action you send to an agent starts as an **agent input** (`ain_…`). Omnara stores the input first, then adds it to the timeline as a durable `agent_input` event when the agent processes it. This lets a busy agent accept new messages without interrupting its current work. There are four input kinds. You only create the first directly; the others are produced by their own endpoints: | `input_kind` | Created by | | - | - | | `content` | `POST /inputs` — the messages this page is about | | `interaction_response` | [Resolving an interaction](/events/interactions) | | `control` | [Stop current work](/agents/overview#stop-current-work) | | `config_change` | [Changing the config mid-conversation](#change-the-config-mid-conversation) | ```bash theme={null} export BASE="$OMNARA_API/orgs/$ORG/projects/$PROJ" export AGENT="agt_v4qtrwz3jehcyd5n6a2bfgik7m" ``` ## Send a message An agent accepts up to 50 pending messages across queued and steering inputs; further submissions return `409` with code `agent_backlog_full` until messages are processed or canceled, while child lifecycle reports are always delivered. > [tab] API > > The examples assume the client, `orgID`, and `projectID` setup from the [quickstart](/quickstart), with the `$AGENT` ID above available as `agentID` for the SDK. The CLI has no command for sending input — use the REST API or the SDK. > > ```bash REST theme={null} > curl "$BASE/agents/$AGENT/inputs" \ > -H "Authorization: Bearer $OMNARA_TOKEN" \ > -H "Content-Type: application/json" \ > -H "Idempotency-Key: reply-8f2c1a" \ > -d '{ > "content_blocks": [ > { "type": "text", "text": "Also check whether the deadline moved." } > ] > }' > ``` > > ```typescript SDK theme={null} > const input = await sdk.createAgentInput({ > client, > path: { orgID, projectID, agentID }, > headers: { 'Idempotency-Key': 'reply-8f2c1a' }, > body: { > content_blocks: [ > { type: 'text', text: 'Also check whether the deadline moved.' }, > ], > }, > }) > console.log(input.data.agent_input) > ``` > > ```json theme={null} > { > "agent_input": { > "id": "ain_2bfgik7mv4qtrwz3jehcyd5n6a", > "state": "received", > "delivery_mode": "queued", > "...": "..." > } > } > ``` > > `state: received` means Omnara accepted the input. When the agent processes it, the state becomes `resolved` and a matching event appears on the timeline. An input can instead become `canceled` if it is withdrawn before the agent processes it. > > If your system retries requests, send an `Idempotency-Key` to avoid delivering the same message twice. > > > [!NOTE] > > > > Full schema and playground: [Create agent input](/api-reference/endpoints/agents/create-agent-input). > [tab] Dashboard > > Open the agent, type a message in the composer, and click **Send**. ## Attach files and images An input can include files alongside text. Inline files are base64-encoded, with limits of 10 MiB per file, 24 MiB combined, and 20 files per input: ```bash REST theme={null} jq -n --arg data "$(base64 < chart.png)" '{ content_blocks: [ { type: "text", text: "Does this chart match the numbers you found?" }, { type: "media", media_type: "image/png", filename: "chart.png", data: $data } ] }' | curl "$BASE/agents/$AGENT/inputs" \ -H "Authorization: Bearer $OMNARA_TOKEN" \ -H "Content-Type: application/json" \ -d @- ``` ```typescript SDK theme={null} import { readFile } from 'node:fs/promises' const chart = await readFile('chart.png') await sdk.createAgentInput({ client, path: { orgID, projectID, agentID }, body: { content_blocks: [ { type: 'text', text: 'Does this chart match the numbers you found?' }, { type: 'media', media_type: 'image/png', filename: 'chart.png', data: chart.toString('base64'), }, ], }, }) ``` Omnara accepts common images, PDFs, text files, spreadsheets, and Office documents. It stores each file as an [artifact](/events/artifacts) and replaces the inline data with a `media_ref` in the timeline. ## Queued vs steering `delivery_mode` decides when a busy agent receives a message: * **`queued`** (default) waits until the agent is ready to start new work. Use it for a new task or follow-up. * **`steering`** is delivered at the next model call, ahead of queued messages, and starts a new [turn](/events/streaming#group-events-into-turns). Use it for an urgent correction such as “stop using the staging database.” The other input kinds — interaction responses, cancels, config changes — never wait in the queue: they're delivered with a third mode, `immediate`, that you can't choose. Only `content` inputs take `queued` or `steering`. ```bash REST theme={null} curl "$BASE/agents/$AGENT/inputs" \ -H "Authorization: Bearer $OMNARA_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "delivery_mode": "steering", "cancel_open_interactions": true, "content_blocks": [ { "type": "text", "text": "Ignore the 2024 draft — it was superseded." } ] }' ``` ```typescript SDK theme={null} await sdk.createAgentInput({ client, path: { orgID, projectID, agentID }, body: { delivery_mode: 'steering', cancel_open_interactions: true, content_blocks: [ { type: 'text', text: 'Ignore the 2024 draft — it was superseded.' }, ], }, }) ``` If the agent is idle, both modes deliver immediately; the difference only exists under load. For a steering message, set `cancel_open_interactions: true` when the correction makes an open approval or question irrelevant. Omnara cancels those interactions before delivering the message. This field is only valid with `delivery_mode: "steering"`. ## Inspect and manage the queue The dashboard shows waiting inputs above the composer. Through the API, you can also list waiting inputs, change their order or delivery mode, or cancel them. ### List waiting inputs ```bash REST theme={null} curl "$BASE/agents/$AGENT/inputs/backlog" \ -H "Authorization: Bearer $OMNARA_TOKEN" ``` ```typescript SDK theme={null} const backlog = await sdk.listQueuedBacklogInputs({ client, path: { orgID, projectID, agentID }, }) console.log(backlog.data) ``` ```json theme={null} { "data": [ { "id": "ain_2bfgik7mv4qtrwz3jehcyd5n6a", "state": "received", "delivery_mode": "queued", "...": "..." }, { "id": "ain_4qtrwz3jehcyd5n6a2bfgik7mv", "state": "received", "delivery_mode": "queued", "...": "..." } ], "next_cursor": null } ``` Similarly, you can cancel a queued input, change its position, or switch it between queued and steering delivery. Promoting is idempotent: it also returns `200` if the input is already steering or the agent has already taken it. Other invalid state transitions return `409`. > [!NOTE] > > Full schemas and playground: [List waiting backlog inputs](/api-reference/endpoints/agents/list-waiting-backlog-inputs) · [Cancel](/api-reference/endpoints/agents/cancel-queued-backlog-input) · [Move](/api-reference/endpoints/agents/move-queued-backlog-input) · [Promote](/api-reference/endpoints/agents/promote-queued-input-to-steering) · [Demote](/api-reference/endpoints/agents/demote-steering-input-to-queued). ## Change the config mid-conversation You can change which [config](/agents/configuration) an agent uses without restarting its conversation. Send the new config source to switch models, tools, permissions, or instructions. Omnara validates the config and records the change on the timeline. This is not available in the dashboard yet. ```bash CLI theme={null} npx omnara agents update {agent-id} --file agent-v2.yaml ``` ```bash REST theme={null} jq -n --rawfile src agent-v2.yaml '{source: $src, source_format: "yaml"}' | curl "$BASE/agents/$AGENT/config" \ -H "Authorization: Bearer $OMNARA_TOKEN" \ -H "Content-Type: application/json" \ -d @- ``` ```typescript SDK theme={null} import { readFile } from 'node:fs/promises' const source = await readFile('agent-v2.yaml', 'utf8') await sdk.updateAgentConfig({ client, path: { orgID, projectID, agentID }, body: { source, source_format: 'yaml' }, }) ``` The change takes effect at the next model call. The dashboard marks it in the conversation with an **Agent configuration changed** divider. > [!NOTE] > > Full schema and playground: [Update agent config](/api-reference/endpoints/agents/update-agent-config). ## Who said that? Actors and attribution Every input is attributed to whoever authenticated the request. When a person calls the API with their own token, that's exactly what you want. But often the people talking to your agent aren't Omnara users at all — they're your customers, typing into your chat frontend or ticketing system. Your backend relays their messages with its own token, so without help the timeline would credit every message to that one backend identity. An **actor** solves this. It's not another kind of account — it's a lightweight identity record for an external user, and passing `actor` on an input names who actually spoke: ```bash REST theme={null} curl "$BASE/agents/$AGENT/inputs" \ -H "Authorization: Bearer $OMNARA_SERVICE_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "content_blocks": [ { "type": "text", "text": "Customer says the export is still failing." } ], "actor": { "provider_tenant_id": "acme-support", "provider_user_id": "user-3841", "display_name": "Dana R." } }' ``` ```typescript SDK theme={null} await sdk.createAgentInput({ client, path: { orgID, projectID, agentID }, body: { content_blocks: [ { type: 'text', text: 'Customer says the export is still failing.' }, ], actor: { provider_tenant_id: 'acme-support', provider_user_id: 'user-3841', display_name: 'Dana R.', }, }, }) ``` Omnara reuses an actor when its `provider_tenant_id` and `provider_user_id` match an existing record. You can also pass an actor when [resolving an interaction](/events/interactions) or [stopping current work](/agents/overview#stop-current-work). Hosted integrations record senders with `provider: "integration"`, a stable platform namespace as `provider_tenant_id`, and the sender’s platform user ID as `provider_user_id`. Slack uses `slack:`, Discord uses `discord`, and GitHub uses `github:github.com`. Within a project, the same person shares an actor across configured bots and integration kinds on that platform. Projects, Slack workspaces, and platforms keep separate identities. Actors are attribution records, not permissions. Their saved `metadata.source_label` identifies the platform in the timeline even after an integration is deleted. Actors belong to a project and can be listed, fetched, or created through the API. Omit `actor` when the request is authenticated as an Omnara user; their account already provides the attribution. > [!NOTE] > > Full schemas and playground: [List project actors](/api-reference/endpoints/actors/list-project-actors) · [Upsert an external actor](/api-reference/endpoints/actors/upsert-an-external-actor) · [Get a project actor](/api-reference/endpoints/actors/get-a-project-actor). ## Inputs from your own app Your service chooses which agent receives each provider event and submits an ordinary input. Pass an `actor` to attribute the message, and use a stable `Idempotency-Key` for retries. Your service owns the conversation-to-agent mapping; Omnara does not need an integration registration. See [Custom integrations](/integrations/custom-integrations) for the complete input, custom-tool and interaction flow. ## Next > [card] [Streaming events](/events/streaming) > > Watch your input get admitted and answered, live > [card] [Approvals & questions](/events/interactions) > > Resolve the prompts that pause an agent # Streaming events Source: https://docs.omnara.com/events/streaming Read the agent timeline Every agent has one ordered timeline of inputs, model responses, tool results, and context checkpoints. Use `GET /events` to read its history, or stream it with Server-Sent Events to follow new activity live. In the dashboard, this timeline is the agent's conversation view. This page explains how to read it over the API when building your own UI or automation. ```bash theme={null} export BASE="$OMNARA_API/orgs/$ORG/projects/$PROJ" export AGENT="agt_v4qtrwz3jehcyd5n6a2bfgik7m" ``` ## Read the timeline `GET /events` returns events in chronological order. Set `after_sequence=0` to start at the beginning: ```bash theme={null} curl "$BASE/agents/$AGENT/events?after_sequence=0&limit=100" \ -H "Authorization: Bearer $OMNARA_TOKEN" ``` ```json theme={null} { "data": [ { "id": "evt_qtrwz3jehcyd5n6a2bfgik7mv4", "sequence": 1, "event_kind": "agent_input", "...": "..." }, { "id": "evt_5n6a2bfgik7mv4qtrwz3jehcyd", "sequence": 2, "event_kind": "model_output", "...": "..." } ], "next_after_sequence": 2, "has_more": false } ``` To fetch the next page, pass `next_after_sequence` as `after_sequence`. Stop when `has_more` is `false`. To start with the newest events, use `before_sequence=0` instead and pass each `next_before_sequence` value into the next request. `limit` defaults to 100 and accepts up to 500. > [!NOTE] > > Full schema and playground: [List events](/api-reference/endpoints/events/list-events). ## What each event carries Every event has an `id`, a `sequence` number, a turn, and an `event_kind`. There are four event kinds: * **`agent_input`** — something entered the conversation: a message, an interaction answer, a cancel, or a config change. [Sending input](/events/sending-input) covers each one. * **`model_output`** — one completed model response, with a `stop_reason` and any tool calls the model requested. `stop_reason: max_tokens` schedules continuation after an output limit; treat that output as ongoing work. Tool calls settle individually before the next model request. Later output or cancellation determines when the turn ends. * **`tool_result`** — the outcome of one tool call (`succeeded`, `failed`, `denied`, or `canceled`). Its `tool_call_id` matches the call in the model output that requested it. * **`context_checkpoint`** — a summary Omnara uses to carry earlier context forward when a conversation grows long. Depending on its kind, an event can contain text, reasoning, tool-call data, structured results, or a reference to a file stored as an [artifact](/events/artifacts). Exact fields are in the [List events](/api-reference/endpoints/events/list-events) reference. ## Stream it live ```bash theme={null} curl -N "$BASE/agents/$AGENT/events/stream?stream_deltas=true" \ -H "Authorization: Bearer $OMNARA_TOKEN" ``` The response is a standard `text/event-stream`. The SSE `event` name tells you what each frame contains: | SSE event | Contains | Saved? | | - | - | - | | `agent_input` | An input added to the timeline | Yes | | `model_output` | A completed model response | Yes | | `tool_result` | A completed tool call's result | Yes | | `context_checkpoint` | A summary of earlier context | Yes | | `tool_call_update` | A tool call ID, its agent ID, a reported lifecycle state, and an optional interaction update | No | | `model_output_delta` | A live preview while the model responds | No | | `error` | A stream-closing error; retry only `service_unavailable` | No | Saved events use their `sequence` as the SSE `id`. Heartbeat comments (`:` lines) arrive every 10 seconds while the stream is idle; they only keep the connection open and can be ignored. While a connection remains open, the server keeps it caught up from its last delivered saved sequence. After a disconnect, reconnecting with `Last-Event-ID` catches up every saved event after that cursor. A simplified exchange looks like: ```text theme={null} event: model_output_delta data: {"model_call_context_id":"mcc_jehcyd5n6a2bfgik7mv4qtrwz3","event":{"kind":"text_delta","delta":"Comparing the two drafts"},"...":"..."} event: model_output id: 3 data: {"id":"evt_5n6a2bfgik7mv4qtrwz3jehcyd","sequence":3,"event_kind":"model_output","content_blocks":[{"type":"text","text":"Comparing the two drafts, three obligations changed…"}],"...":"..."} : heartbeat ``` `tool_call_update` frames are included for every tool type and contain `tool_call_id`, `agent_id`, and `state`. When a transition opens, resolves, or cancels an interaction, they also include `interaction_update` with its `id`, `interaction_kind`, and `state`. They may arrive out of order and are not replayed. After the stream connects, including after a reconnect, query `GET /tool-calls` for current state. A parent's stream does not carry its subagents' timelines, but it does carry their `tool_call_update` frames from every depth, with `agent_id` naming the subagent. Because every question and permission request is attached to a tool call, a frame arrives whenever a subagent asks a question, waits on permission, calls a custom tool, or has one of those resolved or canceled. Treat it as a signal to query `GET /interactions?include_subagents=true` and `GET /tool-calls?include_subagents=true` for current state. After an `error` frame, the server closes that HTTP response. A raw client should reconnect when the stable `code` is `service_unavailable`; other currently documented error codes are terminal and should be surfaced to the caller. Do not decide from the human-readable `error` text. ### Deltas are a preview, not the record With `stream_deltas=true`, the stream includes partial text, reasoning, and tool-call arguments while the model responds. Use these deltas to show progress, but do not treat them as durable history: they are not saved or replayed after a reconnect. When the completed `model_output` event arrives, replace the preview with it. Deltas and their final events share `model_call_context_id`; tool-call previews and results also share `tool_call_id`, so you can match them reliably. Exact delta frame shapes are in the [Stream events](/api-reference/endpoints/events/stream-events) reference. ### Resuming after a disconnect Raw HTTP clients should reconnect after a network failure or an unexpected end of the response. Set `Last-Event-ID` to the last saved sequence you processed. You can also pass the same value as `after_sequence`: ```bash theme={null} curl -N "$BASE/agents/$AGENT/events/stream?stream_deltas=true" \ -H "Authorization: Bearer $OMNARA_TOKEN" \ -H "Last-Event-ID: 3" ``` The server sends every saved event after sequence 3, then continues live. Deltas produced while disconnected are not replayed, but the completed `model_output` event is. The TypeScript SDK owns this loop for you. `openAgentEventStream` follows the durable stream until you stop it, automatically reconnects after transient HTTP, transport, and `service_unavailable` failures, and resumes after the last delivered saved sequence. Other API and contract errors are thrown. ```ts theme={null} const controller = new AbortController() for await (const frame of openAgentEventStream({ client, path: { orgID, projectID, agentID }, query: { stream_deltas: true }, signal: controller.signal, onConnectionStateChange(state) { if (state.state === 'reconnecting') clearDeltaPreviews() if (state.state === 'connected' && state.reconnected) refreshOpenToolCalls() }, })) { applyFrame(frame) } ``` Use the abort signal when you need to stop the stream promptly; breaking out of the loop also closes it. A reconnect can miss best-effort delta and tool lifecycle frames, so discard connection-scoped previews and refresh any authoritative state you project outside the saved event timeline. > [!NOTE] > > Full schema and playground: [Stream events](/api-reference/endpoints/events/stream-events). ## Group events into turns Events are useful for live updates. For conversation history, it is often easier to group them into **turns**. A turn starts when an input enters the timeline and includes the resulting model responses and tool activity. Each agent numbers its turns with `turn_sequence`. List turns newest-first: ```bash theme={null} curl "$BASE/agents/$AGENT/turns?limit=20" -H "Authorization: Bearer $OMNARA_TOKEN" ``` ```json theme={null} { "data": [ { "id": "trn_trwz3jehcyd5n6a2bfgik7mv4q", "agent_id": "agt_v4qtrwz3jehcyd5n6a2bfgik7m", "turn_sequence": 2, "event_count": 9, "opening_events": [ { "sequence": 12, "event_kind": "agent_input", "...": "..." } ], "latest_event": { "sequence": 20, "event_kind": "model_output", "...": "..." }, "latest_semantic_event": { "sequence": 20, "event_kind": "model_output", "...": "..." }, "started_at": "2026-08-03T19:12:40Z", "updated_at": "2026-08-03T19:14:02Z" } ], "next_before_turn_sequence": 1 } ``` The response includes enough to preview each turn without fetching all of its events: * `opening_events` shows what started the turn. * `latest_semantic_event` is the latest event useful to show in a conversation preview. * `latest_event` is the newest event overall, including internal processing events. To fetch the next page of older turns, pass `next_before_turn_sequence` as `before_turn_sequence`. When it is `null`, you have reached the first turn. To load all events in a turn, page backward from its end: ```bash theme={null} curl "$BASE/agents/$AGENT/turns/trn_trwz3jehcyd5n6a2bfgik7mv4q/events?limit=100" \ -H "Authorization: Bearer $OMNARA_TOKEN" ``` Events are chronological within each page. Pass `next_before_sequence` into the next request until it is `null`. A typical conversation UI lists turns, loads a turn's events when opened, and appends new events from the live stream. > [!NOTE] > > Full schemas and playground: [List turns](/api-reference/endpoints/events/list-turns) · [List turn events](/api-reference/endpoints/events/list-turn-events). ## Next > [card] [Approvals & questions](/events/interactions) > > What to do when the stream shows the agent waiting on a human > [card] [Artifacts](/events/artifacts) > > View and download files referenced in the timeline # Event webhooks Source: https://docs.omnara.com/events/webhooks Event webhook bodies and generated SDK schemas Event webhooks send JSON POST requests with `event` and `data` matching the [event stream](/events/streaming), excluding token previews, heartbeats, and stream errors. They are managed independently of agent configuration. ## Configure Create a webhook with `POST /orgs/{orgID}/projects/{projectID}/event-webhooks`: ```json theme={null} { "scope": { "type": "profile", "agent_profile_id": "aprf_5n6a2bfgik7mv4qtrwz3jehcyd" }, "url": "https://example.com/events", "event_types": ["tool_call_update"] } ``` Each webhook watches a project, profile, or single agent. Profile webhooks cover existing and future agents of that profile; agent webhooks do not include descendants. Matching scopes are additive; overlapping subscriptions with the same URL and signing-secret ID queue only one delivery per event. Each project, profile, or agent can have up to 16 directly attached webhooks, with one webhook per URL. Listing requires project read permission; changes require project management permission. See the API reference to [list](/api-reference/endpoints/events/list-event-webhooks-for-one-scope), [update](/api-reference/endpoints/events/update-event-webhook-settings), or [delete](/api-reference/endpoints/events/delete-an-event-webhook) webhooks. In the dashboard, manage webhooks from the profile’s Webhooks tab or the agent’s sidebar. Inherited webhooks are shown read-only; project webhooks are managed through the API. To receive events from launch onward, configure a profile webhook before launching its agents. The required `event_types` allowlist selects `agent_input`, `model_output`, `tool_result`, `context_checkpoint`, or `tool_call_update`. New webhooks and filter changes affect future events, with no historical replay. URL/signing changes apply to pending attempts too. Removing the selected webhook stops its pending attempts without transferring them to another webhook; in-flight requests may finish. Deleting a profile removes its webhooks. Deliveries retry independently for up to ten minutes, with a five-second request timeout and exponential backoff with jitter. Return `2xx` to acknowledge receipt. Events may arrive more than once or out of order; deduplicate deliveries by `Webhook-Id` and custom tool execution by tool-call ID. Signing is optional. Set `signing_secret_id` to a project-available generic secret generated with `openssl rand -base64 32`. Webhook deliveries are suppressed for deleted projects and organizations, including events produced by their deletion. Every request includes `Webhook-Id` (stable across retries) and `Webhook-Timestamp` (Unix seconds for that attempt). Signed requests also include `Webhook-Signature`. Verify the signature against the raw request body before parsing it, using [Standard Webhooks](https://github.com/standard-webhooks/standard-webhooks/blob/main/spec/standard-webhooks.md). ## Receive and verify The SDK includes the TypeScript type `EventWebhookPayload` and the Zod schema `zEventWebhookPayload` for validating incoming webhook payloads. Install `@omnara/sdk` and `standardwebhooks`. Set `WEBHOOK_SIGNING_SECRET` to the same base64 value stored in the webhook's signing secret. This Node.js server verifies the raw body before validating the payload; missing, invalid, or expired signatures are rejected. ```typescript theme={null} import { createServer } from 'node:http' import { buffer } from 'node:stream/consumers' import { zEventWebhookPayload } from '@omnara/sdk/zod' import { Webhook } from 'standardwebhooks' const secret = process.env.WEBHOOK_SIGNING_SECRET if (!secret) throw new Error('set WEBHOOK_SIGNING_SECRET') const verifier = new Webhook(secret) createServer(async (request, response) => { if (request.method !== 'POST' || request.url !== '/webhook') { response.writeHead(404).end() return } try { const rawBody = await buffer(request) let payload try { const verified = verifier.verify(rawBody, { 'webhook-id': String(request.headers['webhook-id'] ?? ''), 'webhook-timestamp': String(request.headers['webhook-timestamp'] ?? ''), 'webhook-signature': String(request.headers['webhook-signature'] ?? ''), }) payload = zEventWebhookPayload.parse(verified) } catch { response.writeHead(400).end('invalid webhook') return } console.log('received', payload.event) response.writeHead(204).end() } catch { response.writeHead(500).end() } }).listen(3000) ``` Expose `/webhook` over public HTTPS. This snippet only validates and acknowledges events. For custom-tool execution and duplicate handling, see the runnable [custom-tool webhook example](https://github.com/omnara-ai/omnara/tree/main/examples/custom-tool-webhook). ## Payload examples Timeline events include event, agent, project, organization, and turn identifiers, an event sequence, and a creation timestamp. The examples below include all required fields; the OpenAPI schemas describe optional fields and content-block variants. > [accordion] Agent input (`agent_input`) > > ```json theme={null} > { > "event": "agent_input", > "data": { > "id": "evt_5n6a2bfgik7mv4qtrwz3jehcyd", > "org_id": "org_5n6a2bfgik7mv4qtrwz3jehcyd", > "project_id": "proj_5n6a2bfgik7mv4qtrwz3jehcyd", > "agent_id": "agt_5n6a2bfgik7mv4qtrwz3jehcyd", > "turn_id": "trn_5n6a2bfgik7mv4qtrwz3jehcyd", > "turn_sequence": 1, > "is_opening_event": true, > "sequence": 1, > "event_kind": "agent_input", > "agent_input_id": "ain_5n6a2bfgik7mv4qtrwz3jehcyd", > "input_kind": "content", > "content_blocks": [ > { > "type": "text", > "text": "Summarize the report." > } > ], > "created_at": "2026-09-18T12:00:00Z" > } > } > ``` > [accordion] Model output (`model_output`) > > ```json theme={null} > { > "event": "model_output", > "data": { > "id": "evt_5n6a2bfgik7mv4qtrwz3jehcyd", > "org_id": "org_5n6a2bfgik7mv4qtrwz3jehcyd", > "project_id": "proj_5n6a2bfgik7mv4qtrwz3jehcyd", > "agent_id": "agt_5n6a2bfgik7mv4qtrwz3jehcyd", > "turn_id": "trn_5n6a2bfgik7mv4qtrwz3jehcyd", > "turn_sequence": 1, > "is_opening_event": false, > "sequence": 3, > "event_kind": "model_output", > "model_call_context_id": "mcc_5n6a2bfgik7mv4qtrwz3jehcyd", > "stop_reason": "end_turn", > "content_blocks": [ > { > "type": "text", > "text": "The report shows steady growth." > } > ], > "created_at": "2026-09-18T12:00:00Z" > } > } > ``` > [accordion] Tool result (`tool_result`) > > ```json theme={null} > { > "event": "tool_result", > "data": { > "id": "evt_5n6a2bfgik7mv4qtrwz3jehcyd", > "org_id": "org_5n6a2bfgik7mv4qtrwz3jehcyd", > "project_id": "proj_5n6a2bfgik7mv4qtrwz3jehcyd", > "agent_id": "agt_5n6a2bfgik7mv4qtrwz3jehcyd", > "turn_id": "trn_5n6a2bfgik7mv4qtrwz3jehcyd", > "turn_sequence": 1, > "is_opening_event": false, > "sequence": 3, > "event_kind": "tool_result", > "tool_call_id": "tcl_5n6a2bfgik7mv4qtrwz3jehcyd", > "outcome": "succeeded", > "content_blocks": [ > { > "type": "text", > "text": "Ticket created." > } > ], > "created_at": "2026-09-18T12:00:00Z" > } > } > ``` > [accordion] Context checkpoint (`context_checkpoint`) > > ```json theme={null} > { > "event": "context_checkpoint", > "data": { > "id": "evt_5n6a2bfgik7mv4qtrwz3jehcyd", > "org_id": "org_5n6a2bfgik7mv4qtrwz3jehcyd", > "project_id": "proj_5n6a2bfgik7mv4qtrwz3jehcyd", > "agent_id": "agt_5n6a2bfgik7mv4qtrwz3jehcyd", > "turn_id": "trn_5n6a2bfgik7mv4qtrwz3jehcyd", > "turn_sequence": 1, > "is_opening_event": false, > "sequence": 3, > "event_kind": "context_checkpoint", > "context_checkpoint_id": "ccp_5n6a2bfgik7mv4qtrwz3jehcyd", > "summarized_through_event_sequence": 2, > "summary": "The user requested a report summary.", > "created_at": "2026-09-18T12:00:00Z" > } > } > ``` > [accordion] Tool-call update (`tool_call_update`) > > ```json theme={null} > { > "event": "tool_call_update", > "data": { > "tool_call_id": "tcl_5n6a2bfgik7mv4qtrwz3jehcyd", > "agent_id": "agt_5n6a2bfgik7mv4qtrwz3jehcyd", > "state": "awaiting_permission", > "interaction_update": { > "id": "int_5n6a2bfgik7mv4qtrwz3jehcyd", > "interaction_kind": "permission", > "state": "open" > } > } > } > ``` `tool_call_update` is a notification to fetch the tool call or interaction for its current details. It carries a state snapshot, not the tool arguments or a timeline sequence. For custom execution, handle `ready` updates and deduplicate by tool-call ID. Subagents follow the same scope rules and send their own agent IDs. When a permission or question opens or closes, `interaction_update` includes its ID, kind, and state (`open`, `resolved`, or `canceled`). Omission means no interaction change. Track interactions by ID; a terminal update takes precedence over a late `open` update. Fetch interactions when you need the full form. # Browser agent Source: https://docs.omnara.com/examples/browser-agent A long-running agent that signs in to your web apps and works in a real browser, overnight if needed It signs in to your web apps with credentials stored as Omnara [secrets](/organization/secrets), then works in a real browser: pulling data out of dashboards, filling in forms, or working through a queue overnight. It can run one set job on a schedule or take whatever tasks your team sends it in Slack or Discord, with screenshots in its updates. Passwords never pass through the model, and it enters authenticator app codes itself, so it can sign in unattended. It runs on a large machine that's kept, browser and sign-ins included, until you archive the agent, and it asks before anything you haven't allowed it to do on its own. ## Build it Give this prompt to Claude Code, Codex, Cursor, or any other coding agent. It asks about the work, which sites the agent signs in to and how, whether it runs on a schedule, and whether you want it in Slack or Discord. Then it builds the agent in your Omnara project and has it sign in to each site once. ```text theme={null} Read https://raw.githubusercontent.com/omnara-ai/omnara/main/examples/browser-agent/SKILL.md and follow it to build my browser agent. ``` See [`examples/browser-agent`](https://github.com/omnara-ai/omnara/tree/main/examples/browser-agent) on GitHub for how it works and what to do if a site blocks it. # Email agent Source: https://docs.omnara.com/examples/email-agent An agent with its own email address that people can email, Cc, or @mention Gives an agent its own email address on [Primitive](https://www.primitive.dev). Email it a question and it replies in the thread. Cc it with `@assistant` in the message to pull it into a conversation, or Bcc it for a private answer. Each email thread is its own Omnara conversation, so you can watch and steer every thread from the console. There's no server to run: a small Primitive Function wakes the agent, and the agent reads and sends mail through Primitive's MCP server. The defaults are open so you can try it right away: anyone can email it, and it replies on its own. Setup shows how to limit who can wake it, change how Cc works, or add more agents with their own addresses. ## Deploy Give this prompt to Claude Code, Codex, Cursor, or any other coding agent. It walks you through setup, creates the agent in your Omnara project, and deploys the function to Primitive. ```text theme={null} Read https://raw.githubusercontent.com/omnara-ai/omnara/main/examples/email-agent/SKILL.md and follow it to deploy the email agent. ``` To see how it works or adapt it, browse [`examples/email-agent`](https://github.com/omnara-ai/omnara/tree/main/examples/email-agent) on GitHub. # LinkedIn signal agent Source: https://docs.omnara.com/examples/linkedin-signal-agent A daily digest of the LinkedIn posts worth your time on any topic Scrapes the last 24 hours of LinkedIn posts matching your searches, plus new posts from the companies and people you track, and delivers at most five worth your time. It uses Apify's hosted MCP server and needs no LinkedIn account or cookie. ## Deploy Give this prompt to Claude Code, Codex, Cursor, or any other coding agent. It walks you through setup, creates the agent in your Omnara project, and runs it once. ```text theme={null} Read https://raw.githubusercontent.com/omnara-ai/omnara/main/examples/linkedin-signal-agent/SKILL.md and follow it to deploy the LinkedIn signal agent. ``` To see how it works or adapt it, browse [`examples/linkedin-signal-agent`](https://github.com/omnara-ai/omnara/tree/main/examples/linkedin-signal-agent) on GitHub. # Examples Source: https://docs.omnara.com/examples/overview Agents you can deploy with one prompt, plus code samples for specific features Every example lives in the repository's [`examples/`](https://github.com/omnara-ai/omnara/tree/main/examples) directory. If you're evaluating Omnara, start with the [Quickstart](/quickstart). ## Agents you can deploy Give one prompt to Claude Code, Codex, Cursor, or any other coding agent, and it sets the agent up in your Omnara project and runs it once. Each is an `agent.yaml` plus a `SKILL.md`, so any of them is a starting point for your own. ### GTM agents > [card] [X signal agent](/examples/x-signal-agent) > > A daily digest of the X posts worth your time on any topic: customers, competitors, or your category > [card] [Reddit signal agent](/examples/reddit-signal-agent) > > The same digest for Reddit, across searches and the subreddits where your audience gathers > [card] [LinkedIn signal agent](/examples/linkedin-signal-agent) > > The same digest for LinkedIn, across keyword searches and the companies and people you track > [card] [SEO audit agent](/examples/seo-audit-agent) > > Audits a page for SEO, answer engines, and AI search citations, with a PDF report of fixes ready to paste ### Product > [card] [PostHog analytics agent](/examples/posthog-analytics-agent) > > A daily usage report from PostHog: active users, top events, and anything unusual ### Engineering > [card] [SRE agent](/examples/sre-agent) > > A read-only production investigator for AWS logs, metrics, and infrastructure, plus your code if you connect it > [card] [Coding agent in Slack](/examples/slack-coding-agent) > > Mention it in Slack, Discord, or on a GitHub pull request, and it fixes the issue and opens or updates a pull request > [card] [PR review agent](/examples/pr-review-agent) > > Reviews your GitHub pull requests against a checkout of the repository and leaves inline findings ### Operations > [card] [Browser agent](/examples/browser-agent) > > Signs in to your web apps with stored credentials and works there in a real browser, overnight if needed > [card] [Email agent](/examples/email-agent) > > An agent with its own email address that people can email, Cc, or @mention, with each thread as its own conversation ### Creative > [card] [Video generation agent](/examples/video-generation-agent) > > Short videos from a brief, built with Remotion, checked frame by frame, and delivered as an MP4 ## Code samples Small projects that each show one feature in code. | Example | What it builds | What it teaches | | - | - | - | | [`cli-agent`](https://github.com/omnara-ai/omnara/tree/main/examples/cli-agent) | An interactive chat CLI built on the TypeScript SDK | Bootstrapping a project, grants, and a profile with `@omnara/sdk`, choosing a pool or local machine, streaming and resuming a conversation | | [`custom-tool-webhook`](https://github.com/omnara-ai/omnara/tree/main/examples/custom-tool-webhook) | A TypeScript receiver that executes a custom tool and submits its result | Signed webhooks driving custom tool execution, signature verification, duplicate handling | | [Machine with mounted storage](/examples/mounted-storage) | An agent with filesystem access to a SharePoint document library | Bring-your-own machines: daemon in a container, mounts, targeting a machine by name | | [`unikraft-machine`](https://github.com/omnara-ai/omnara/tree/main/examples/unikraft-machine) | A Unikraft Cloud machine image carrying the Omnara daemon | Building custom pool images | # PostHog analytics agent Source: https://docs.omnara.com/examples/posthog-analytics-agent A daily usage report from your PostHog project Queries your PostHog project through PostHog's MCP server and reports yesterday against the trailing 7-day average: active users, top events, and anything unusual. It's read-only against PostHog. ## Deploy Give this prompt to Claude Code, Codex, Cursor, or any other coding agent. It walks you through setup, creates the agent in your Omnara project, and runs it once. ```text theme={null} Read https://raw.githubusercontent.com/omnara-ai/omnara/main/examples/posthog-analytics-agent/SKILL.md and follow it to deploy the PostHog analytics agent. ``` To see how it works or adapt it, browse [`examples/posthog-analytics-agent`](https://github.com/omnara-ai/omnara/tree/main/examples/posthog-analytics-agent) on GitHub. # PR review agent Source: https://docs.omnara.com/examples/pr-review-agent Reviews your GitHub pull requests against a checkout of the repository and leaves inline findings A GitHub App that reviews pull requests on every new pull request, when mentioned, or both. It reads the change against a fresh checkout of your repository, publishes one review with inline findings and one-click suggestions, and answers replies to its comments. It focuses on bugs, security, breaking changes, and missing tests, and follows your repository's `AGENTS.md` and `CONTRIBUTING.md`. ## Deploy Give this prompt to Claude Code, Codex, Cursor, or any other coding agent. It asks when the agent should review, creates it in your Omnara project, and walks you through registering its GitHub App. ```text theme={null} Read https://raw.githubusercontent.com/omnara-ai/omnara/main/examples/pr-review-agent/SKILL.md and follow it to deploy the PR review agent. ``` To see how it works or adapt it, browse [`examples/pr-review-agent`](https://github.com/omnara-ai/omnara/tree/main/examples/pr-review-agent) on GitHub. # Reddit signal agent Source: https://docs.omnara.com/examples/reddit-signal-agent A daily digest of the Reddit threads worth your time on any topic Scrapes the last 24 hours of Reddit posts about your topic, across searches and the subreddits where your audience gathers, and delivers at most five threads worth your time. It uses Apify's hosted MCP server, so there's no Reddit API approval to wait for. ## Deploy Give this prompt to Claude Code, Codex, Cursor, or any other coding agent. It walks you through setup, creates the agent in your Omnara project, and runs it once. ```text theme={null} Read https://raw.githubusercontent.com/omnara-ai/omnara/main/examples/reddit-signal-agent/SKILL.md and follow it to deploy the Reddit signal agent. ``` To see how it works or adapt it, browse [`examples/reddit-signal-agent`](https://github.com/omnara-ai/omnara/tree/main/examples/reddit-signal-agent) on GitHub. # SEO audit agent Source: https://docs.omnara.com/examples/seo-audit-agent Audits a web page for SEO, answer engines, and AI search citations, and delivers a PDF report Give it a URL and it audits the page for classic search (SEO), answer engines (AEO), and AI search citations (GEO): what crawlers can see before and after JavaScript, whether AI crawlers are blocked, Lighthouse speed, metadata and structured data, and how the page compares with the pages that compete for its query. The report grades each area, ranks the fixes, and writes them ready to paste, delivered as a PDF plus a markdown copy. It needs no API keys; a Google key adds real-user speed data, and an OpenAI key adds measured AI search citations. ## Deploy Give this prompt to Claude Code, Codex, Cursor, or any other coding agent. It asks which page to audit first and whether to add any keys, creates the agent in your Omnara project, and runs the first audit. ```text theme={null} Read https://raw.githubusercontent.com/omnara-ai/omnara/main/examples/seo-audit-agent/SKILL.md and follow it to deploy the SEO audit agent. ``` To see how it works or adapt it, browse [`examples/seo-audit-agent`](https://github.com/omnara-ai/omnara/tree/main/examples/seo-audit-agent) on GitHub. # Coding agent in Slack Source: https://docs.omnara.com/examples/slack-coding-agent Mention it in Slack, Discord, or on a GitHub pull request, and it fixes the issue and opens or updates a pull request Describe a bug, paste an error, or link an issue in Slack or Discord, or mention it on a GitHub pull request, and it works on a fresh clone of your GitHub repository, makes a focused fix, and opens a pull request. Reply in the thread to ask for changes, and it splits larger tasks across copies of itself. ## Deploy Give this prompt to Claude Code, Codex, Cursor, or any other coding agent. It walks you through setup, creates the agent in your Omnara project, and connects it to Slack. ```text theme={null} Read https://raw.githubusercontent.com/omnara-ai/omnara/main/examples/slack-coding-agent/SKILL.md and follow it to deploy the Slack coding agent. ``` To see how it works or adapt it, browse [`examples/slack-coding-agent`](https://github.com/omnara-ai/omnara/tree/main/examples/slack-coding-agent) on GitHub. # SRE agent Source: https://docs.omnara.com/examples/sre-agent A read-only production investigator for AWS, with optional code access Ask it about a symptom or paste an alert, and it works through AWS (infrastructure plus CloudWatch logs and metrics), and your source code if you connect it, to report the most likely cause with evidence. Its AWS access is read-only, so it never changes production. ## Deploy Give this prompt to Claude Code, Codex, Cursor, or any other coding agent. It walks you through setup, creates the agent in your Omnara project, and runs it once. ```text theme={null} Read https://raw.githubusercontent.com/omnara-ai/omnara/main/examples/sre-agent/SKILL.md and follow it to deploy the SRE agent. ``` To see how it works or adapt it, browse [`examples/sre-agent`](https://github.com/omnara-ai/omnara/tree/main/examples/sre-agent) on GitHub. # Video generation agent Source: https://docs.omnara.com/examples/video-generation-agent Short videos from a brief, built with Remotion and delivered as an MP4 Describe the video you want and it builds it in code with [Remotion](https://www.remotion.dev), reviews stills from its own render to catch clipped text and glitches, and delivers the MP4. Reply to ask for changes, or attach a logo or music for it to use. It needs no API keys, only a machine pool, and defaults to Claude Opus 5.5. ## Deploy Give this prompt to Claude Code, Codex, Cursor, or any other coding agent. It walks you through setup, creates the agent in your Omnara project, and has it make a first video. ```text theme={null} Read https://raw.githubusercontent.com/omnara-ai/omnara/main/examples/video-generation-agent/SKILL.md and follow it to deploy the video generation agent. ``` To see how it works or adapt it, browse [`examples/video-generation-agent`](https://github.com/omnara-ai/omnara/tree/main/examples/video-generation-agent) on GitHub. # X signal agent Source: https://docs.omnara.com/examples/x-signal-agent A daily digest of the X posts worth your time on any topic Searches the last 24 hours of X (Twitter) posts about a topic you pick and delivers at most five worth your time, each with why it matters and a suggested action. Use it to find customers, watch competitors, or follow anything you're curious about. ## Deploy Give this prompt to Claude Code, Codex, Cursor, or any other coding agent. It walks you through setup, creates the agent in your Omnara project, and runs it once. ```text theme={null} Read https://raw.githubusercontent.com/omnara-ai/omnara/main/examples/x-signal-agent/SKILL.md and follow it to deploy the X signal agent. ``` To see how it works or adapt it, browse [`examples/x-signal-agent`](https://github.com/omnara-ai/omnara/tree/main/examples/x-signal-agent) on GitHub. # Custom integrations Source: https://docs.omnara.com/integrations/custom-integrations Connect your own service using agent inputs, custom tools and interactions Your service can connect any system to Omnara through the public API. It receives provider events, chooses which agent should receive them, executes custom tools, and displays questions or approvals wherever you choose. No Omnara integration needs to be registered. Use a project-authorized API key with the operator role for runtime requests. Creating configs requires project management permission. The [authentication guide](/api/authentication) explains credentials and API paths. The examples below use paths relative to `/orgs/{orgID}/projects/{projectID}`. ## Receive events and launch agents Keep a mapping from your conversation ID to an Omnara agent ID. On the first event, [launch an agent](/agents/overview#launch-an-agent) with your config and an initial message. To attribute that first message to a customer, use [`initial_input` with an `actor`](/agents/overview#attach-integrations-and-an-initial-input-atomically). Supply an `Idempotency-Key` so a retried launch returns the same agent. Retry the first event by retrying the launch; its key does not deduplicate a later input. For later events, send `POST /agents/{agentID}/inputs`: ```json theme={null} { "content_blocks": [{"type": "text", "text": "The customer replied on ticket 42: please check again."}], "actor": {"provider_tenant_id": "helpdesk", "provider_user_id": "customer-7"} } ``` `actor` identifies who spoke. API keys may supply it; user-authenticated requests use the signed-in user. Conversation IDs and any parent/child relationships belong to your application. Include relevant context in the input for the model and keep the authoritative routing map in your service. Use a stable input idempotency key that includes the provider and event identity. Input deduplication is scoped to the receiving agent, so events from different systems must not share a key accidentally. Keep the request unchanged on retry. Choose [queued or steering delivery](/events/sending-input#queued-vs-steering) explicitly. Steering can also cancel open interactions with `cancel_open_interactions: true`. Your service owns webhook verification, event persistence and retry policy. Sending an input does not grant tools or create an integration subscription. When an ordinary API message is admitted in automatic mode, it clears the selected built-in interaction handler for future prompts. A manually pinned destination remains selected. ## Let the model act in your system Declare ordinary [custom tools](/tools/custom) in the config. For example: ```yaml theme={null} tools: post_ticket_update: type: custom description: Post an update to the support ticket associated with this agent. input_schema: type: object properties: text: {type: string} required: [text] additionalProperties: false ``` Find ready calls as described in [Custom tools](/tools/custom#find-calls-waiting-for-you), execute the call in your service, then submit its result to `POST /agents/{agentID}/tool-calls/{toolCallID}/result`. Resolve the ticket from your agent mapping and enforce authorization in your service. A model-supplied identifier is not authorization. The result can contain your message IDs, thread IDs or other structured data. If posting creates a conversation, save its mapping to the agent before accepting replies. Those replies use the same input API; Omnara needs no separate follow registration. MCP tools are another option when your service exposes an MCP server. Use tool-call IDs to reconcile remote side effects. After an uncertain completion response, read back the call and recorded result. Repeated completion returns `409`; do not repeat a remote post just because its result response was lost. ## Present and resolve interactions List `GET /agents/{agentID}/interactions`, render each open interaction's `request` form wherever your application chooses, and submit answers through `POST /agents/{agentID}/interactions/{interactionID}/resolve`. [Questions and approvals](/events/interactions) remain available in the dashboard. The first accepted answer wins; later conflicting answers are rejected. Your application decides where to present an interaction and retains its remote message IDs. If it handles all presentation, leave built-in integration interaction handlers unconfigured. When configured, those handlers still deliver to the captured destination. In automatic mode, admitting an ordinary API message clears the selected built-in handler for future interactions; pinning it with `auto_select: false` preserves the choice. An optional `destination` or `presentation_receipt` describes Omnara's built-in presentation, not a delivery request to your service. Use [event stream](/events/streaming) updates as hints, then refresh the durable lists. Reconcile on startup, on reconnect and every few minutes: notifications can be missed. Deduplicate presentations by interaction ID and close your copies when interactions resolve or cancel. A failed external delivery must not prevent someone from answering through the dashboard or API. # Discord Source: https://docs.omnara.com/integrations/discord Connect your bot and choose messaging tools, subscriptions and approval destinations Create a Discord integration from your project's **Integrations** page to start agents from mentions and continue conversations in Discord threads. ## Connect your bot In the [Discord Developer Portal](https://discord.com/developers/applications): 1. Create an application and choose its name in Discord. Copy the **Application ID** and **Public Key** from **General Information**. 2. Under **Bot**, copy the bot token, or use **Reset Token** to create one. 3. Enable **Bot → Privileged Gateway Intents → Message Content Intent** before connecting. This is required to read replies that do not mention the bot. 4. Under **Installation**, enable **Guild Install**. Omnara receives server messages through Gateway; this integration does not support DMs. The bot needs **View Channels**, **Read Message History**, **Send Messages**, **Add Reactions**, **Create Public Threads** and **Send Messages in Threads** in the channels it uses. Grant **Attach Files** if agents will upload files. ### Dashboard Choose **Discord bot** on your project's **Integrations** page. Keep the suggested Omnara name `discord-bot` or enter your own, such as `support`. Enter the Application ID, public key and bot token, then choose **Create and connect**. Omnara saves the credential for you and retrieves the bot User ID and display name from the token. After connecting, save the displayed **Interactions Endpoint URL** under **General Information** in Discord. **Add bot to server** opens Discord with the permissions above already selected. Choose a server you manage, then select **Profiles for mentions** in Omnara and select **Save changes**. The dashboard requires the public key. It verifies interaction callbacks for profile menus, questions and approvals; it is different from the bot token used to read and send messages. Discord controls who can install the bot and access its conversations. Public bots are supported: people in accessible conversations can launch the offered profiles and answer agent questions and approvals without Omnara project membership, subject to the agent's configured permissions. For internal use, turn off **Public Bot** under **Bot** in the Developer Portal when available, and restrict server/channel access in Discord. This setting limits new installations; review existing servers separately. See Discord's [bot authorization guidance](https://docs.discord.com/developers/topics/oauth2#bot-authorization-flow). ### API Create the integration with `POST /orgs/{orgID}/projects/{projectID}/integrations`: ```json theme={null} {"name":"support","integration_kind":"discord_thread","settings":{}} ``` Save the token as a project-available [generic secret](/organization/secrets), with `material: {"kind":"generic","value":"your-bot-token"}`. Configure the saved integration through `POST /orgs/{orgID}/projects/{projectID}/integrations/{integrationID}/setup`: ```json theme={null} { "expected_setup_revision": 1, "provider_tenant_id": "111111111111111111", "credential_secret_id": "sec_aeaqcaibaeaqcaibaeaqcaibae", "provider_config": {"public_key": "YOUR_64_CHARACTER_HEX_PUBLIC_KEY"} } ``` Use the integration's current `setup_revision`. `provider_tenant_id` is the Application ID; neither it nor the bot User ID is the guild ID. Setup discovers and saves the bot User ID as `provider_account_ref`. If supplied explicitly, it must match the token. Reconnecting cannot change the saved application or bot. Omnara manages one Gateway connection per integration. This supports bots in fewer than [2,500 servers](https://docs.discord.com/developers/events/gateway#sharding); larger bots require sharding, which is not currently supported. Omit `provider_config` during token rotation to keep the current settings. Supplying it replaces the settings, so include every field you intend to retain. The API permits omitting the public key for messaging with a single launch profile. Profile menus and Discord questions or approvals require it and a verified [Interactions Endpoint URL](#questions-approvals-and-profile-menus). Install the bot through Discord with the permissions listed above. Each saved integration has an independent runtime. Integrations can use the same bot credentials, including in different projects, but duplicated sessions consume provider capacity. Overlapping launch behavior can create duplicate responses. Omnara does not merge these independently configured integrations. Unrelated messages without role mentions or a mention of this bot use inbox capacity but make no Discord REST requests. Omnara checks exact thread subscriptions and pending launches before looking up the channel. Role mentions require a guild-role lookup unless the bot user is also mentioned. Bot mentions require a channel lookup and share the bot's [REST quota](https://docs.discord.com/developers/topics/rate-limits) with replies. Identity checks happen only after routing finds a recipient, or before replying to a mention with no matching subscription or launcher. Attachment downloads happen only after routing finds a recipient. ## Launch from mentions Choose **Profiles for mentions** in the integration's settings. Mentions work in every server where this bot is installed and permitted; manage server and channel access in Discord. All of those servers use this Omnara project's profile choices. Mention either the bot user or its own Discord-managed role. Omnara verifies role ownership with Discord; matching names, other assigned roles and `@everyone` do not trigger a launch. Mentioning both the user and its role is one request. When starting an agent inside an existing thread or forum post, wait for the bot's response before sending follow-ups. Messages sent while Omnara is identifying that thread can be missed, including while Discord lookups are retrying. Once the agent has been attached to the thread, this limitation no longer applies. For the API, put `launcher` inside `settings` and send it to `PUT /orgs/{orgID}/projects/{projectID}/integrations/{integrationID}`: ```json theme={null} { "settings": { "launcher": {"profiles": ["aprf_aeaqcaibaeaqcaibaeaqcaibae"]} } } ``` Profiles are ordered, distinct public IDs. Discord launchers do not accept server or channel filters. Updates replace `settings`; send `{"settings":{}}` to remove the mention launcher. A mention in a parent channel creates or reuses the public thread attached to that message. A mention already inside a thread uses that thread. One offered profile starts immediately; several profiles produce a menu and start only the selected profile. Anyone in the conversation can choose. The menu expires after an hour; send follow-ups after selecting, because earlier replies may be missed. The launcher derives the agent's tools and interaction handler for the thread, preserving existing explicit entries in the profile config, and attaches a thread subscription. Human messages steer the agent and cancel open interactions. Bot and webhook messages are ignored. The bot adds an eyes reaction after a message is accepted for an agent. Enable **Add Reactions** on the bot's role if you installed it before this permission was included in setup. A failed reaction does not prevent the agent from receiving the message. Change **Profiles for mentions** directly on the integration page and select **Save changes** to update future launches; existing agents keep their configs. Selecting profiles enables mention launches without a separate enable switch. An empty selection leaves schedules and existing conversations available. With no matching subscription or launcher, a mention gets a reply saying the bot isn't available for requests; other messages receive no response. That reply is skipped when another integration uses the same bot. ## Scheduled threads Choose a server text or announcement channel. The opening-message template is limited to 2,000 characters. Use **Add schedule** on the integration page to post a template opening message and launch one profile in its new thread. Each schedule selects its own profile and destination channel, independently of mention settings. The tools, reply subscription and interaction handler are ready when the agent starts. Schedules can coexist with mention launches. See [scheduled integration launches](/integrations/overview#start-a-new-thread-on-a-schedule) for setup, template variables and recovery behavior. ## Thread tools and subscriptions A mention or scheduled launch supplies `read` and `post_message` for its assigned thread. `read` supports `before` and `limit`; `post_message` takes `content` and an optional `paths` array of up to 10 files. Use exact `/artifacts/` or `/memory//` paths; directories and globs are not supported. For example, `{"content":"Update","paths":["/memory/reports/weekly.pdf"]}` posts the message and file directly to that thread. Omit `paths` for a text-only message. The model supplies neither channel nor thread IDs. Adding these tools to an agent without an assigned conversation is insufficient; calls fail before contacting Discord. `post_message` returns the message's actual `channel_id` (the thread ID when posting in a thread), along with `message_id` and `thread_id`. The launcher subscribes the agent to replies. Posting does not change that subscription. To independently forward messages to an existing agent, send this body to the [integration subscriptions API](/integrations/overview#subscribe-a-conversation). This does not assign or change the conversation used by the integration's tools. ```json theme={null} { "agent_id": "YOUR_AGENT_ID", "conversation": { "thread_id": "555555555555555555" } } ``` Thread subscriptions need only `thread_id`; `guild_id` and the parent `channel_id` are optional. Supplied IDs are checked for valid format, but subscription creation does not verify them against Discord or retain them as routing restrictions. Create/list responses return only `thread_id` for a thread. For a channel subscription, supply `channel_id` and omit `thread_id`. Channel subscriptions receive mentioned thread starters, not ordinary replies in their threads. Removing the sending tool or changing config does not remove subscriptions. Use **Stop forwarding** on the integration page or DELETE the subscription to remove it, including while the integration is disconnected. ## Questions, approvals and profile menus Configure the integration's public key, then set the Discord registration's **Interactions Endpoint URL** to: ```text theme={null} https://YOUR_OMNARA_HOST/api/integrations/discord/APPLICATION_ID/interactions ``` Use the numeric Discord Application ID, not Omnara's `itg_…` ID. One public endpoint can verify callbacks for independently configured integrations. Each menu or agent interaction captures its owning integration, so its answer is routed only there. Require successful [signed PING verification](https://docs.discord.com/developers/interactions/receiving-and-responding) before offering profile menus or external approvals. For an agent assigned to the `support` integration's thread, select `interaction_handlers: {support: {}}` in its config to expose the handler. For manual configs, also select `list_interaction_handlers` and `set_interaction_handler` under `tools` to let the model choose a destination. The Discord launcher adds the handler and both tools automatically, preserving explicit settings. The handler uses the thread assigned when the integration launches the agent. Select it with `{"handler":"support","args":{}}`; the model does not supply channel, thread or server IDs. The list tool shows the assigned destination. Adding a handler to a manual config does not create an assignment. With automatic selection enabled, the last eligible content input admitted into a new turn selects its handler. Inputs from the dashboard, API, or an integration without an eligible handler select dashboard-only delivery. Ordinary scheduled instructions and internal agent messages/reports without an integration origin preserve the selection. An explicit integration origin takes precedence, including a scheduled launch in a new thread. Waiting inputs do not change the current turn. Set `auto_select: false` with `set_interaction_handler` to pin a choice, or `true` to resume automatic selection; omission preserves the mode. The list tool reports the mode. Questions remain answerable in the dashboard if Discord presentation fails. There is no Gateway fallback for interaction answers or automatic slash-command registration. Discord interaction notifications run independently of the waiting agent. Delivery is best effort: overload can delay or drop notifications, and restarts can interrupt sending. Omnara retries only safe failures, with a bounded retry budget. If delivery may have succeeded, it does not automatically post another copy; a crash during sending can leave a prompt missing or unconfirmed. The interaction remains answerable in the [dashboard and API](/events/interactions) regardless of delivery. ## Restarts and deployments An integration can retain its verified credentials while its Gateway connection is failing. The integration detail page shows the latest connection error and earliest retry time for the current setup and credential version. **Refresh status** rereads the recorded failure; it does not start a retry. The failure remains until a new attempt starts. After correcting the Discord settings, use **Reconnect account** to make the integration eligible for a new attempt without waiting for the old retry time. A missing error does not prove the Gateway is connected. The list and setup responses do not include this runtime status; API clients read the optional `runtime_failure` from **Get integration**. A worker saves its Discord session and last committed event sequence in Postgres. The event receipt and sequence are saved together. When another worker takes over, it resumes that session and Discord can replay missed events; repeated message receipts are deduplicated. A database lease prevents two workers from committing for the same saved integration. Normal shutdown releases that lease; after a crash, a replacement waits for it to expire, normally within 30 seconds. Keep replacement workers healthy during routine deployments, and allow the old workers to shut down gracefully. Postgres and Redis must remain available. Discord retains replay history for a limited time: a long outage or an invalidated session can still leave a gap. The worker records that it must start a fresh session; restarting is not a guarantee that Discord retains every missed message. HTTP interactions use the API service separately. Keep a healthy API instance receiving requests while another drains. Before a production rollout, use a dedicated test guild to check a mention, a plain reply in the resulting thread, and another reply after restarting the test worker. Confirm that they reach the same agent without duplicate inputs. Also verify a signed interaction callback if using approvals or profile menus. The automated suite uses local provider fixtures; it does not replace this live check of the bot's intents and guild permissions. # GitHub Source: https://docs.omnara.com/integrations/github Give agents scoped pull-request tools and configure PR event routing Use your own GitHub App to let an agent read a pull request or post comments. Choose its tools independently from incoming events. Answer questions and approvals in Omnara's dashboard; GitHub has no interaction handler. The [interactions API](/events/interactions) is also available. ## Connect your GitHub App ### Dashboard Open **Integrations** in your project and choose **GitHub PR review**. Keep the suggested Omnara name `github-bot` or enter your own, such as `reviews`. Choose whether the GitHub App belongs to your personal account or an organization, then select **Continue to GitHub**. Choose the App's public name on GitHub's registration page; it is separate from the Omnara integration name. Omnara supplies the webhook, event subscriptions and required permissions. The registration belongs to you. After registration, Omnara saves the credentials and checks account access automatically. Select **Choose repositories on GitHub** to grant access in the same tab; GitHub returns you to Omnara to continue setup. Omnara selects a single available account automatically; if several are available, choose which one to connect. Select **Connect integration** to finish. If organization approval is pending, return later using the saved credential and check access again. Nothing launches until you choose an agent profile in **Pull requests** and select **Save changes**. > [!WARNING] > > PR-open launches, mention launches and comments that steer agents require repository > write access. This applies to PRs from forks too; outside contributors cannot start > agents just by opening a PR. Choose a profile whose tools, machine access and secrets are > appropriate for untrusted input, and review its tool permissions before enabling > incoming work. The new GitHub App is private. To install it outside its owning account, change its visibility in GitHub App settings. Guided registration supports GitHub.com. Choose **Enter App details** for a registration you already own or manual setup. Complete registration within an hour. If credential conversion or saving fails, recover the existing App by generating a private key and setting a webhook secret in GitHub, then enter those values through manual setup. A saved credential remains available even if the Omnara integration's settings changed during registration. Guided discovery supports user and organization Apps and installations. Enterprise-owned Apps and enterprise-level installations report an unsupported setup instead of prompting repeated retries. Organization installations within GitHub Enterprise Cloud still use the ordinary organization flow. Manage repository access in GitHub. Optionally restrict the launcher to one repository in the integration settings. This limits incoming launches; it does not narrow the installation access granted through [Git credentials](#git-credentials). ### Manual setup 1. [Register a GitHub App](https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/registering-a-github-app) you control. Give it repository **Pull requests: Read-only**, or **Read & write** if agents will comment, and **Issues: Read-only** for incoming PR conversation comments. For Git credentials, also request **Contents: Read & write**. To show a check on the PR while the agent works, also request **Checks: Read & write**. 2. Enable webhooks with URL `https://YOUR_OMNARA_HOST/api/integrations/github/events` and a webhook secret. Subscribe to the [events](https://docs.github.com/en/webhooks/webhook-events-and-payloads) `pull_request`, `issue_comment`, `pull_request_review` and `pull_request_review_comment`. 3. Install the App on the repositories you want to use. Record its numeric **App ID**, numeric **Installation ID**, RSA private key and webhook secret. App ID and Installation ID are different values. In the dashboard, choose **Enter App details**, enter these values and connect. Omnara creates the credential for you, or you can select a saved credential. Use the displayed webhook URL for your deployment and test delivery after connecting. The following secret and setup requests are only needed for API setup. ### API Create the integration with `POST /orgs/{orgID}/projects/{projectID}/integrations`: ```json theme={null} {"name":"reviews","integration_kind":"github_pr","settings":{}} ``` [Create a secret](/organization/secrets#create-a-secret) available to the Omnara project, with this `material` shape: ```json theme={null} { "kind": "github_app_credentials", "app_id": "12345", "private_key": "-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----", "webhook_secret": "your-webhook-secret" } ``` Configure the returned integration at `POST /orgs/{orgID}/projects/{projectID}/integrations/{integrationID}/setup`: ```json theme={null} { "expected_setup_revision": 1, "provider_tenant_id": "12345", "provider_account_ref": "67890", "credential_secret_id": "sec_aeaqcaibaeaqcaibaeaqcaibae" } ``` Use its current `setup_revision`. The credential's App ID must match `provider_tenant_id`; `provider_account_ref` is the installation ID. Omnara verifies the app, installation and bot identity before saving. Reconfigure after renaming the GitHub registration to refresh its bot login. Verified numeric identities remain fixed. Disconnecting or deleting the Omnara integration requires no GitHub request and changes no comments or reviews in GitHub. The GitHub App ID header selects candidate credentials; the signature and installation identity authenticate the event. Verified installation identity routes ordinary events to each matching active Omnara integration. Independent integrations may use the same installation; they are not automatically merged. Configure an active integration before testing ping. ## Select PR tools After saving the `reviews` integration, select operations by qualified tool name: ```yaml theme={null} tools: int__reviews__read: {} int__reviews__discussion_comment: permission: {mode: always_ask} int__reviews__review_comment: permission: {mode: always_ask} int__reviews__reply: permission: {mode: always_ask} int__reviews__start_review: {} int__reviews__submit_review: permission: {mode: always_ask} int__reviews__discard_review: permission: {mode: always_ask} ``` The GitHub launcher assigns one pull request to the agent. Every tool operates on that PR, so the model supplies neither repository nor PR identifiers. Adding these tools to an agent without an assigned PR is insufficient; calls fail before contacting GitHub. Subscriptions independently control incoming events and do not assign or change the tools' PR. Machine Git authentication uses the separate `git_credentials` capability described below. `read` returns PR details by default. Its `section` can select `diff`, `files`, `discussion_comments`, `review_comments`, `reviews`, `review`, `pending_review` or `review_threads`. Comments, files and submitted `reviews` use `page` and `limit`; follow `next_page` until it is absent, including after an empty page. `reviews` includes the overall review body, state (such as `APPROVED`, `CHANGES_REQUESTED` or `DISMISSED`), author, commit and submission time. Unpublished pending reviews are excluded. `review_threads` uses `cursor` and `limit` instead of `page`. Pass the returned `next_cursor` to read the next page; an absent cursor means the end. Each thread includes `is_resolved`, `is_outdated`, its path/line and, when available, a `comment_id` linking to the first available comment in `review_comments` or `reply`. Read comment bodies and replies with `review_comments`. Lists default to 30 items and accept a `limit` of 1–100. Oversized `diff` reads fail with guidance to use `files` and follow `next_page`. Individual file patches may be incomplete for large or binary changes. `discussion_comment` posts a PR conversation comment using `body`. `review_comment` posts on the diff using `body`, `commit_id`, `path`, `line` and `side` (`LEFT` or `RIGHT`); provide `start_line` and `start_side` together for a multiline comment. `reply` uses `comment_id` and `body` to reply to an existing review comment. These publish individual comments. To build a review incrementally, first use `read` with `section: pending_review`. It returns this bot's pending review, or `null`. GitHub allows one pending review per bot per PR, including when multiple integrations use the same bot. `start_review` creates a draft at `commit_id` and returns its `id`. Pass that `review_id` instead of `commit_id` to `review_comment` to add unpublished findings one at a time. A pending review can prevent immediate inline comments or replies; continue, publish, or discard the draft before retrying a rejected operation. Use `read` with `section: review` and `review_id` for the review's summary and state, and `section: review_comments` with that ID to page through its comments. `submit_review` publishes those findings with `body` as the summary. It submits a COMMENT review; it does not approve or request changes. `discard_review` deletes only the specified pending review and its unpublished comments. Inspect the draft before either action. If submission requires approval, its tool call contains the review ID and summary; the inline findings remain in GitHub. GitHub stores the draft. Stopping or restarting an agent neither publishes nor deletes it. An uncertain network result requires read-back before repeating the action: `pending_review` for draft creation/deletion, `review` for submission, or `review_comments` for a draft comment. Existing agents need the new operations enabled in their configuration; their saved tool selection does not change automatically. ## Git credentials Agents can use a connected integration for HTTPS Git access on `github.com`: ```yaml theme={null} git_credentials: integration: reviews repository_ids: [123456789] # optional read_only: true # optional ``` This works independently of PR tools, subscriptions and PR assignment. The GitHub launcher adds its integration automatically; an explicit choice in the selected profile takes precedence. Subagents do not inherit this capability, including from their selected profile. Configure machine access separately and run ordinary Git commands with HTTPS repository URLs. Credentials inherit **all repositories granted to the connected installation** unless `repository_ids` lists a subset; find an ID with `gh api repos/OWNER/REPO --jq .id`. They are read-only unless **Let agents push code** (`code_write`) is enabled on the integration and `read_only` isn't set. The assigned PR and `launcher.repository_id` do not narrow these credentials. Existing PR tools retain their assigned-PR restrictions. See GitHub's [installation token scope](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-an-installation-access-token-for-a-github-app). Apps registered through Omnara get **Contents: Read & write**. If an App only has **Read-only**, its write credentials fail while **Let agents push code** is enabled; grant **Read & write** in the App's settings and have the installation owner approve it. Pushes remain subject to GitHub's repository rules and can't change files under `.github/workflows`. See GitHub's [permission guidance](https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/choosing-permissions-for-a-github-app). Git credentials require an up-to-date `omnarad` on every machine. Omnara does not enforce a minimum daemon version for this feature. Older daemons ignore the credential setting and may use existing machine credentials instead. The helper handles `github.com` HTTPS authentication in enabled agent processes and replaces other GitHub HTTPS helpers there. Other processes, hosts and SSH authentication are unaffected; global Git config is unchanged. To opt out on a machine, run `OMNARA_GIT_CREDENTIALS=0 omnarad install`; the installer persists `git_credentials_disabled`. Commands requesting Git credentials fail during preparation on an opted-out machine. This also applies to GitHub-launched agents, since their launcher adds the capability automatically. The helper supplies tokens to local workload code; it does not isolate credentials from other code running as the same OS user. Tokens refresh on use; disconnecting an integration stops future issuance but does not immediately revoke already issued tokens or remove cloned files. ## Configure incoming PR work In the dashboard's **Pull requests** section, choose one profile and whether to start agents from PR openings, mentions or both, then select **Save changes**. These controls remain editable on the integration page. To mention the bot, use `@`, such as `@review-helper` for `https://github.com/apps/review-helper`. Use the GitHub App's slug, not the Omnara integration name (`reviews` in these examples). To forward events from one PR to an existing agent, send this body to `POST /orgs/{orgID}/projects/{projectID}/integrations/{integrationID}/subscriptions`: ```json theme={null} { "agent_id": "YOUR_AGENT_ID", "conversation": {"repository_id": 123456, "pull_request": 42} } ``` The integration forwards PR conversation comments, inline review comments, submitted reviews and commits from PR synchronization events. PR-open events only trigger launches; they are not forwarded through subscriptions. Use **Stop forwarding** on the integration page to remove a subscription. For PR-open and mention launches through the API, update the saved integration with `PUT /orgs/{orgID}/projects/{projectID}/integrations/{integrationID}`: ```json theme={null} { "settings": { "launcher": { "trigger": "both", "profile": "aprf_aeaqcaibaeaqcaibaeaqcaibae" } } } ``` The launcher derives the PR tools and Git credentials, adding missing capabilities and preserving explicit entries from the profile. It attaches a subscription for the triggering PR. An unrestricted launcher applies to repositories granted to this installation. Set `launcher.repository_id` to a numeric repository ID string to narrow it. Use `trigger: "mention"` for mention-only launches, or `"pull_request_opened"` for PR-open-only launches. With `"both"`, either event can start the PR's agent; later mentions reach that same agent. New dashboard setups and the SDK's `profileIntegrationSetup` helper default to both. GitHub accepts exactly one profile. Updates contain only `settings`; name and kind are immutable. PR-open launches, comments and reviews require a human sender with repository write access. PRs opened by bots or outside contributors do not auto-launch; with mentions enabled, a human with write access can mention the bot to start a review. Comment and review checks apply even when the launcher is disabled. Other comments remain readable as context. Once a PR is subscribed, new commits continue to reach its agent. Human discussion/review input steers the agent and cancels open interactions; PR opens and synchronization events queue. Bot comments, edits and unrelated issue comments do not become agent input. Omnara adds an 👀 reaction to PR discussion comments and inline review comments after accepting them for an agent. This means the input was accepted, not that the agent has finished. Reactions are best effort; a failed reaction does not prevent the agent from receiving the comment. With the **Checks** permission, the PR also shows a check named after your GitHub App and integration, such as `my-app (github-bot)`: **Working** while the agent, its subagents or commands they started are running, **Waiting for input** when it needs an answer or approval, and **Finished** when it stops. The check follows work that GitHub starts: the PR opening, new commits and comments the agent receives. Follow-ups sent only through Omnara (the dashboard or API) may not update a finished check. Like reactions, check updates are best effort. GitHub does not automatically retry failed webhook deliveries. If Omnara cannot persist an event and returns a non-2xx response, explicitly [redeliver it from GitHub](https://docs.github.com/en/webhooks/using-webhooks/handling-failed-webhook-deliveries) after recovery, manually or through GitHub's API. Once a receipt is accepted, Omnara retries processing failures through its internal inbox. Once a receipt has failed permanently, redelivering the same event does not reset its retry budget; send a fresh comment or mention instead. For an active configured installation, Omnara commits eligible signed callbacks before returning HTTP 204. A persistence failure returns 503; it does not schedule a GitHub retry. Manual redelivery with the same delivery ID is accepted once and deduplicated while that receipt remains stored. Signed pings and callbacks for unmanaged or disconnected installations, along with irrelevant events, can receive 204 without creating agent work. Omnara does not run an automatic GitHub redelivery scheduler. Completed and failed raw receipts are eligible for cleanup seven days after finishing. Their transport deduplication window ends when they are deleted; agent conversation history is preserved. # Integrations Source: https://docs.omnara.com/integrations/overview Connect project-owned integrations, subscribe conversations, and choose tools and interaction handlers An integration connects your project to a service such as Slack, Discord or GitHub. It owns the provider setup, credentials and optional launch behavior. Create it once, then select its tools and handlers in agent configs and attach conversations to agents. Customers running their own service can use the ordinary [inputs, custom tools and interaction APIs](/integrations/custom-integrations). ## Create an integration Select your project in the project switcher, open **Integrations**, and select an integration type. If you already have integrations, choose **Add integration** first. Enter its unique name and connection details together on the setup page, then choose launch profiles after connecting. If connection fails, retrying reuses the saved integration and credentials. You can also reopen the saved integration from **Integrations** to finish later. New integrations suggest `slack-bot`, `discord-bot` or `github-bot`; you can change that suggestion before creation. This Omnara name identifies tools and is separate from the App or bot name shown by the provider. The name follows the same rules as an MCP server name: a letter followed by letters, digits or hyphens, up to 32 characters. Names and integration types cannot be changed after creation. The integration type identifies its behavior: `slack_thread`, `discord_thread` or `github_pr`. With the API, send `POST /orgs/{orgID}/projects/{projectID}/integrations` using `integration_kind`, for example: ```json theme={null} {"name":"engineering","integration_kind":"slack_thread","settings":{}} ``` Updates contain only `settings`; the integration name and kind are immutable. `GET /orgs/{orgID}/projects/{projectID}/integration-definitions` lists each registered `integration_kind` and its `capabilities`. Saved integrations use `itg_` IDs; profile choices use `ipc_` IDs, while agent interactions keep `int_` IDs. A saved integration starts disconnected. Connecting verifies its provider identity and saves a reference to encrypted credentials. Add profiles and a launcher when provider events should start agents. Schedules and interaction handlers can be used without an event launcher. The shipped thread and PR tools require the conversation assigned by their integration when launching an agent. Two integrations configured with the same bot remain independent setups, with separate credential updates and lifecycle. Avoid overlapping launchers for the same conversation: duplicate menus, agents or replies are possible. The detail page shows setup state, launch settings, available capabilities and a paginated **Connected conversations** list with each destination agent. Changing offered profiles affects future launches. Disconnecting stops provider access and conversation forwarding. It does not queue incoming events for replay after reconnection. Its conversations remain visible and can still be removed while disconnected. Deleting an integration removes its schedules and subscriptions and releases its credential reference. Neither action removes agents, their history, or messages already posted in the provider. Agents remain usable through the dashboard and API. See the [Slack](/integrations/slack), [Discord](/integrations/discord) and [GitHub](/integrations/github) guides for connection steps and provider permissions. ## Select capabilities independently An integration can supply tools, a subscription capability and one interaction handler. A tool lets the model act; an integration-owned subscription forwards incoming events to an agent; a handler presents questions and approvals outside the dashboard. Configs select tools and handlers. Launch requests or the subscriptions API attach conversations independently of config. The launcher is separate: it decides whether and how to start agents. For example, a Slack integration named `engineering` adds its tools and interaction handler when it launches an agent. The resulting config includes entries like these: ```yaml theme={null} instruction: | Answer requests in the Slack conversation that launched you. model: provider_config: my-provider name: my-model tools: ask_question: {} int__engineering__read: {} int__engineering__post_message: {} list_interaction_handlers: {} set_interaction_handler: {} interaction_handlers: engineering: {} ``` Tools use `int____`, with one entry per integration and operation. Tool entries select enabled state, deferred loading and permissions. Handler entries are empty objects. Slack and Discord handlers use the agent's assigned conversation, so selecting one takes `{"handler":"engineering","args":{}}`. Listing handlers shows only eligible choices and their destinations. Selecting a handler never grants access to another conversation. The catalog exposes the static argument schema in `capabilities.tools[operation].input_schema` and `capabilities.interaction_handler.input_schema`. A provider or scheduled launch records one immutable conversation for that integration and agent. Slack and Discord tools use that conversation; GitHub tools use that pull request. Calls contain only the action and pagination arguments, such as `{"text":"Update"}` or `{"section":"files","page":2}`. These tools fail before contacting the provider if the agent has no assigned conversation. Adding a subscription or selecting an interaction handler does not establish or change sending context. Integrations can also define standalone tools that use the integration's credentials without a launcher, subscription or assigned conversation. The tool's implementation defines which resources it can access; ordinary tool permissions still apply. The integration's credential permissions remain the provider access boundary; Omnara also checks project ownership, current integration status and ordinary [tool permission](/tools/permissions). Names are resolved to immutable integration IDs, not integration types, when the config is compiled. Deleting an integration and reusing its name does not redirect old agents. Re-save profiles that should use the replacement integration before their launchers run; old pinned references are not automatically rebound. Credentials are loaded live, so rotation does not require rewriting configs. A pending tool call cannot silently acquire a different integration, sending context or permission after a config change. ## Subscribe a conversation A subscription connects one integration, one agent and one concrete provider conversation. At most 16 agents can subscribe to the same conversation through one integration; adding another returns HTTP 409. Multiple integrations or conversations can forward to the same agent. Subscriptions belong to the integration: saving, activating or changing an agent config never adds or removes them. The integration and integration-definition catalog expose the optional `capabilities.subscription.conversation_schema`. Integrations without this capability cannot accept subscriptions. Each attachment identifies the integration and one flat provider conversation address. The integration determines which incoming activity it forwards. For an existing agent, send this body to `POST /orgs/{orgID}/projects/{projectID}/integrations/{integrationID}/subscriptions`: ```json theme={null} { "agent_id": "YOUR_AGENT_ID", "conversation": {"channel_id": "C123", "thread_ts": "111.222333"} } ``` For a new agent, include `subscriptions` in its [launch request](/agents/overview#attach-integrations-and-an-initial-input-atomically). Each attachment has `integration_id` and `conversation`. The subscriptions and initial input are committed with the agent before its first step. Attaching only subscriptions does not derive a new config. Use `GET` on the same integration subscriptions path with `limit` and optional `cursor` to list conversations. The response contains `data` and `next_cursor`; each row includes its `isub_…` ID, integration, project, agent ID/name, concrete conversation and creation time. Listing requires project read permission; creation and deletion require project management permission. ### Stop forwarding Open the integration's **Connected conversations** section and choose **Stop forwarding**, or call `DELETE /orgs/{orgID}/projects/{projectID}/integrations/{integrationID}/subscriptions/{subscriptionID}`. This stops future incoming forwarding for that subscription. The agent, sending tools, interaction handlers and history remain. Explicitly attach the conversation again through the API to resume forwarding. Already accepted chooser handoffs are not canceled by this deletion. Repeating an active attachment returns the same subscription. A fresh attachment after deletion receives a new ID, so retrying an old DELETE cannot remove the new attachment. Integration disconnection preserves subscriptions; integration deletion or agent archival removes them. Sending and receiving are independent. Posting a message does not create or restore subscriptions. To start a fresh scheduled agent in a new thread, configure an integration schedule; the launch assigns its sending conversation and reply subscription. ## Launch from provider events [Slack](/integrations/slack) and [Discord](/integrations/discord) create thread agents from mentions. With one offered profile, the agent starts immediately. With several profiles, the bot presents a selection menu and starts only the chosen profile. [GitHub](/integrations/github) can start work from a PR mention or PR creation using one configured profile. A profile launch derives a config with the integration's tools and supported interaction handler, and separately attaches a subscription for that conversation. The profile stays unchanged. Existing explicit entries win as complete entries, including disabled tools and permissions. The launcher adds missing entries only; the capability additions are the same for every conversation using that profile and integration. The destination belongs to the integration-agent context, not the derived config. Launcher settings belong to each integration's schema, published in `capabilities.settings.input_schema`. Settings use public profile IDs and resolve profiles when launching; removing a profile is allowed, and an unavailable profile requires updating the launcher. Use subscriptions to forward to existing agents. A receive-only subscription does not prevent launching. True saved launch ownership prevents a replacement even after archival. The create-agent API accepts `tools` and `interaction_handlers` when deriving a config from a pinned config or profile, plus independent `subscriptions` attachments. Config authoring and integration management require project management permission. Integration credential setup requires a user session or personal access token, except guided GitHub registration and installation discovery, which require a browser session. Organization API keys can perform ordinary runtime work. Integration capabilities and runtime subscriptions are not inherited by subagents. Ordinary model output stays in Omnara. Posting externally requires an integration tool. ## Start a new thread on a schedule Open an integration that supports scheduled threads from the project's **Integrations** page and choose **Add schedule**. Select one profile, the parent channel, the schedule and timezone, an opening-message template, and the task to give the agent. Mention launches and schedules are independent; the same integration can use either or both. For each occurrence, the integration posts the opening message and then launches a fresh agent in its thread. The agent receives thread tools, a reply subscription, and an interaction handler before its first step. The profile's existing explicit entries still win. Ask the agent to post its report using the integration tool; normal model output remains in Omnara. Replies in Monday's thread continue Monday's agent, while Tuesday's run creates a new thread and agent. Both templates support the existing cron variables. `{{.trigger.local_date}}` is the occurrence's scheduled date in its timezone, formatted `YYYY-MM-DD`. The task's `message_template` source and rendered message each have a 65,536 UTF-8 byte limit. Variables can expand, so runtime validation remains authoritative even when the source fits the published schema's `maxLength`. See the provider guide for opening-message limits and supported destinations: [Slack scheduled threads](/integrations/slack#scheduled-threads) or [Discord scheduled threads](/integrations/discord#scheduled-threads). With the API, send `POST /orgs/{orgID}/projects/{projectID}/cron-triggers` with an integration target. The integration catalog publishes the schema for the target's `settings`: ```json theme={null} { "name": "Daily engineering update", "cron": "0 9 * * 1-5", "timezone": "America/Los_Angeles", "target": { "type": "integration", "integration_id": "YOUR_INTEGRATION_ID", "settings": { "agent_profile_id": "YOUR_PROFILE_ID", "channel_id": "C123", "opening_message_template": "Engineering update — {{.trigger.local_date}}", "message_template": "Review today's activity and post a summary in this thread." } } } ``` The schedule shows whether the latest run is queued, processing, completed or failed, alongside its last update time. **Completed** means the integration handled that scheduled occurrence. For the thread integrations, that means the agent started; it does not mean the agent finished its task or posted a report. Run details expire with inbox retention. A disconnected integration skips an occurrence with a visible failure; later scheduled occurrences can run after reconnection. Disabling or deleting a schedule stops future firings; it does not cancel work already accepted by the integration. The target integration is fixed. Settings, including the thread integration's profile, can change for future occurrences; an accepted occurrence retains its settings. Deleting the integration deletes its schedules. Deleting a profile does not delete integration schedules that reference it: those runs fail visibly until the settings are corrected. The thread integration resolves the profile's current configuration while building its launch plan. Once saved, that plan keeps the same configuration across retries. An opening message can remain if the later agent launch fails. Once the thread's launch plan is saved, retries reuse that thread. A reported uncertain send or a failure saving the plan after publication fails the run. If a crash, lost lease, or database outage prevents saving either the plan or the failure, recovery may post another opening and leave the earlier heading unused. Each occurrence still admits at most one agent. Questions and approvals remain available in the dashboard if provider delivery fails. ## Route questions and approvals When an integration exports an interaction handler, its launcher adds that handler and the `list_interaction_handlers` and `set_interaction_handler` tools. Launchers preserve tools and handlers already configured in the profile, including explicit tool permissions and disabled entries. Manual configs can add these built-ins under `tools`. Ordinary configs receive neither automatically. The selected handler can change at runtime without changing the config. The list is paginated and returns eligible handlers, their argument schemas and destinations, plus the current selection independently of the page. The model discovers these through the list tool; Omnara does not append routing information to model requests. The shipped Slack and Discord handlers use the agent's assigned conversation. Select one with empty arguments: ```json theme={null} {"handler":"engineering","args":{}} ``` A subscription alone does not grant interaction access, and adding a handler to a config does not assign a conversation. Anyone able to respond in an eligible provider conversation can answer its questions and approvals without an Omnara account. Use explicit tool permissions to restrict or disable `set_interaction_handler` when the model should not change the selected handler. Use `{"handler":null,"args":{}}` for dashboard only. Automatic selection starts enabled: the last eligible content input admitted to a turn selects its handler or clears the selection if none applies, including dashboard/API messages. Internal agent reports and messages and ordinary cron instructions leave the selection unchanged. An explicit integration origin takes precedence, so a scheduled integration thread selects its handler; an origin without an eligible handler, including GitHub, clears the selection. Receipt alone does not redirect a running turn. Pass `auto_select: false` to pin your choice, or `true` to resume automatic selection; omitting it preserves the current mode. The list and setter return that mode. Each interaction captures its destination when created, so changing the selection does not move an existing prompt. The dashboard remains available if provider presentation fails. Removing a handler, disconnecting its integration, revoking project access to its credentials, or losing its assigned conversation invalidates external callbacks. Captured interaction responses identify the integration and target, with the provider fields in `conversation`, using the same format as subscriptions. See [interactions](/events/interactions) for answering through the API. ## Delivery and failures Once accepted into Omnara's inbox, inbound events retry processing with a bounded budget. Terminal failures release unfinished launch reservations and preserve accepted agents and inputs. Failed receipts remain available for diagnostics until cleanup, which starts no earlier than seven days after failure unless the integration, project or organization is deleted. See [failed integration events](/self-hosting/configuration#failed-integration-events). # Slack Source: https://docs.omnara.com/integrations/slack Launch thread agents from mentions and select Slack tools, subscriptions and approvals Create a Slack integration from your project's **Integrations** page to start agents from mentions and continue conversations in Slack threads. ```mermaid theme={null} graph LR P["Agent profile"] -->|launches| A1["Agent 1"] P -->|launches| A2["Agent 2"] P -->|launches| A3["Agent 3"] A1 <--> T1["Thread A"] A2 <--> T2["Thread B"] A3 <--> DM["DM with bot"] ``` For complete examples you can deploy, see [Coding agent in Slack](/examples/slack-coding-agent), which answers in threads and opens pull requests, and the [X signal agent](/examples/x-signal-agent), which posts a daily digest to a channel. ## Connect Slack ### Dashboard Choose **Slack bot** and keep the suggested Omnara name `slack-bot` or enter your own, such as `engineering`. This permanent name identifies the integration and its tools. * To create a Slack app, enter **Name in Slack**, the name teammates will see, and an app configuration token from [Slack's app settings](https://api.slack.com/apps). Omnara configures the app's URLs and event subscriptions for you. * To use an existing app, choose **Enter app details**, copy its Client ID, Client Secret and Signing Secret from **Basic Information**, and complete the [Slack settings checklist](#existing-app-checklist) below. Choose **Create and connect**, then authorize the app in Slack. Authorization continues in the same tab and returns to the saved integration to choose launch profiles. The bot token obtained through OAuth grants access to that workspace; the app configuration token is only used to create the registration. ### Existing app checklist In [Slack's app settings](https://api.slack.com/apps), use the URLs shown in Omnara or returned by the API setup request: 1. Under **OAuth & Permissions**, add `redirect_uri` to **Redirect URLs**. 2. Under **Event Subscriptions**, enable events, set **Request URL** to `events_url`, and add all of these under **Subscribe to bot events**: * `app_mention` * `app_uninstalled` * `channel_rename` * `group_rename` * `message.channels` * `message.groups` * `message.im` * `message.mpim` * `tokens_revoked` * `user_profile_changed` 3. Under **Interactivity & Shortcuts**, enable interactivity and set **Request URL** to `actions_url`. 4. Under **App Home**, enable the **Messages Tab** and allow users to send messages if you want DMs with the bot. Use HTTP event delivery with **Socket Mode** off. Save the changes before authorizing. OAuth grants permissions; it does not select [event subscriptions](https://docs.slack.dev/apis/events-api/#subscribing-to-event-types). ### CLI or API With the CLI or API, create the Omnara integration first: ```json theme={null} {"name":"engineering","integration_kind":"slack_thread","settings":{}} ``` Submit that body to `POST /orgs/{orgID}/projects/{projectID}/integrations`, then use its returned `itg_…` ID for setup. To create a Slack registration: ```bash CLI theme={null} omnara integrations slack itg_3jehcyd5n6a2bfgik7mv4qtrwz \ --app-name "Release Research" \ --app-configuration-token "xoxe.xoxp-..." ``` ```bash REST theme={null} curl "$OMNARA_API/orgs/$ORG/projects/$PROJ/integrations/itg_3jehcyd5n6a2bfgik7mv4qtrwz/slack-setup" \ -H "Authorization: Bearer $OMNARA_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "app_name": "Release Research", "app_configuration_token": "xoxe.xoxp-..." }' ``` ```typescript SDK theme={null} const setup = await sdk.createIntegrationSlackSetup({ client, path: { orgID, projectID, integrationID: 'itg_3jehcyd5n6a2bfgik7mv4qtrwz' }, body: { app_name: 'Release Research', app_configuration_token: 'xoxe.xoxp-...', }, }) console.log(setup.data.oauth_url) ``` ```json theme={null} { "integration_id": "itg_3jehcyd5n6a2bfgik7mv4qtrwz", "setup_revision": 1, "provider": "slack", "flow_id": "ioaf_aeaqcaibaeaqcaibaeaqcaibae", "slack_app_id": "A0919XJRZ3T", "oauth_url": "https://slack.com/oauth/v2/authorize?client_id=…&scope=…&state=…", "redirect_uri": "https://app.omnara.com/api/integrations/oauth/callback", "events_url": "https://app.omnara.com/api/integrations/slack/events", "actions_url": "https://app.omnara.com/api/integrations/slack/actions", "expires_at": "2026-08-03T21:41:00Z" } ``` For an existing Slack registration: ```bash CLI theme={null} omnara integrations slack itg_3jehcyd5n6a2bfgik7mv4qtrwz \ --client-id "1234567890.0987654321" \ --client-secret "…" \ --signing-secret "…" ``` ```bash REST theme={null} curl "$OMNARA_API/orgs/$ORG/projects/$PROJ/integrations/itg_3jehcyd5n6a2bfgik7mv4qtrwz/oauth/setup" \ -H "Authorization: Bearer $OMNARA_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "client_id": "1234567890.0987654321", "client_secret": "…", "signing_secret": "…" }' ``` ```typescript SDK theme={null} const setup = await sdk.createIntegrationOAuthSetup({ client, path: { orgID, projectID, integrationID: 'itg_3jehcyd5n6a2bfgik7mv4qtrwz' }, body: { client_id: '1234567890.0987654321', client_secret: '…', signing_secret: '…', }, }) console.log(setup.data.oauth_url) ``` For an existing app, complete the [checklist above](#existing-app-checklist) using the returned URLs. Newly created apps already include these settings. Open `oauth_url` before `expires_at`, signed in to Omnara as the user who started setup, and approve installation. The CLI opens this link unless `--no-browser` is supplied. The integration becomes active when its `last_oauth_flow_id` matches the returned flow. Setup updates that specific saved integration and does not create a launcher. For self-hosting, `OMNARA_PUBLIC_URL` must be a public HTTPS origin. Reauthorizing creates a new encrypted project secret and updates the integration to use it. A superseded secret remains in Secrets until you delete it; Omnara prevents deletion while another resource still references it. Disconnecting preserves the credential reference for reconnect, while deleting the integration releases it. ## Start agents from mentions Choose profiles in the integration's launch settings. Mention launches are available wherever the bot can receive messages in the connected workspace. Invite the bot to the channels where it should respond. A channel mention starts work in that thread; a DM to the bot can start work without a mention. With one profile, the agent starts immediately. With several, the first reply asks which profile to use; no agent starts before the selection. Anyone in the conversation can choose. Menus expire after an hour, and follow-up messages sent before the choice may be missed. Edit offered profiles for future requests without changing existing conversations. Selecting profiles enables mention launches; there is no separate enable switch. Leave the selection empty to use only scheduled launches. Removing the last profile removes the mention launcher, without affecting schedules or existing conversations. With no matching subscription or launcher, a mention or DM gets a reply saying the bot isn't available for requests; other messages receive no response. That reply is skipped when another integration uses the same bot. A launch derives a config with read/post tools and a Slack interaction handler, and attaches a thread subscription before the first input. Existing explicit config entries are preserved. Thread replies steer the selected agent and cancel its outstanding interactions. Bot messages are ignored. Overlapping launchers using the same bot are discouraged. For API setup, send the settings to `PUT /orgs/{orgID}/projects/{projectID}/integrations/{integrationID}`: ```json theme={null} { "settings": { "launcher": { "profiles": ["aprf_aeaqcaibaeaqcaibaeaqcaibae"] } } } ``` Profiles are ordered, distinct public IDs. Slack launchers contain only `profiles`; channel access is managed in Slack. Updates replace `settings`; send `{"settings":{}}` to remove the mention launcher. ## Scheduled threads Use **Add schedule** on the integration page to post a template opening message and launch one profile in its new thread. The tools, reply subscription and interaction handler are ready when the agent starts. Schedules can coexist with mention launches. Choose a channel the bot has already joined, not a DM. The opening-message template is limited to 2,000 characters. See [scheduled integration launches](/integrations/overview#start-a-new-thread-on-a-schedule) for setup, template variables and recovery behavior. ## Thread tools and subscriptions A mention or scheduled launch supplies `read` and `post_message` for its assigned thread. `read` accepts `cursor` and `limit`; `post_message` accepts `text` and an optional `paths` array of up to 20 files. Use exact `/artifacts/` or `/memory//` paths; directories and globs are not supported. For example, `{"text":"Update","paths":["/memory/reports/weekly.pdf"]}` posts the message and file directly to that thread. Omit `paths` for a text-only message. Thread reads start at the oldest messages. Pass `next_cursor` back as `cursor` to reach later replies; the first page may not include recent activity. The model cannot choose another channel or thread. Adding these tools to an agent without an assigned conversation is insufficient; calls fail before contacting Slack. The launcher also subscribes the agent to replies. Sending messages does not change subscriptions. Removing the sending tool or changing the config leaves subscriptions intact. Use **Stop forwarding** on the integration page or the [subscriptions API](/integrations/overview#stop-forwarding) to remove one. The subscriptions API can independently forward messages to an existing agent: ```json theme={null} { "agent_id": "YOUR_AGENT_ID", "conversation": {"channel_id": "C123", "thread_ts": "111.222333"} } ``` Slack forwards messages from the subscribed conversation. Attaching a subscription does not assign or change the conversation used by this integration's tools. ## Questions and approvals For an agent assigned to the `engineering` integration's conversation, add `interaction_handlers: {engineering: {}}` to offer Slack questions and approvals. For manual configs, also select `list_interaction_handlers` and `set_interaction_handler` under `tools` to let the model choose a destination. The Slack launcher adds the handler and both tools automatically, preserving explicit settings. The handler uses the conversation assigned when the integration launches the agent. Select it with `{"handler":"engineering","args":{}}`; the model does not supply channel or thread IDs. The list tool shows the assigned destination. Adding a handler to a manual config does not create an assignment or grant another conversation. With automatic selection enabled, the last eligible content input admitted into a new turn selects its handler. Inputs from the dashboard, API, or an integration without an eligible handler select dashboard-only delivery. Ordinary scheduled instructions and internal agent messages/reports without an integration origin preserve the selection. An explicit integration origin takes precedence, including a scheduled launch in a new thread. Waiting inputs do not change the current turn. Set `auto_select: false` with `set_interaction_handler` to pin a choice; set it to `true` to resume automatic selection. Omitting it keeps the current mode. The list tool reports the mode. The dashboard always remains available. Slack interaction notifications run independently of the waiting agent. Delivery is best effort: overload can delay or drop notifications, and restarts can interrupt sending. Omnara retries only safe failures, with a bounded retry budget. If delivery may have succeeded, it does not automatically post another copy; a crash during sending can leave a prompt missing or unconfirmed. The interaction remains answerable in the [dashboard and API](/events/interactions) regardless of delivery. # The open-source managed agent platform Source: https://docs.omnara.com/introduction A self-hostable alternative to Claude Managed Agents and OpenAI's Agents API. Run durable AI agents on any model and any machine. Omnara is an open-source managed agent platform and a self-hostable alternative to Claude Managed Agents and OpenAI's Agents API. Use the API to run AI agents on any model and any machine, with tools, secrets, permissions, and streaming built in. Self-host it for free under Apache 2.0, or use [Omnara Cloud](https://app.omnara.com). ## Use cases Omnara can be used to build agents for internal use cases or external use cases, such as: * Internal slack bots * Code review agents * Agents integrated in a product dashboard Omnara exposes a web interface, CLI, and REST API to enable these use cases. ## Build an agent An agent starts as a small YAML config: ```yaml theme={null} instruction: | You are an engineering assistant. Use your machine to run commands. Ask a question when a task is not clear. model: provider_config: omnara-openrouter name: anthropic/claude-fable-5.1 tools: run_command: {} web_search: {} ask_question: {} machine_sources: - machine_pool_name: default-pool ``` You can create and launch the agent in two ways: * **Dashboard** — click **New agent** in your project in the dashboard. * **Programmatically** - Omnara provides an **API**, **Typescript SDK**, and **CLI** to interact with Omnara The [quickstart](/quickstart) provides a more detailed guide to setting up your first agent. ## Core concepts 1. An **organization** owns shared resources for your team: **projects**, **secrets**, **model providers**, and **machines**. 2. **Projects** contains agents. **Grants** decide which shared resources the project can use. 3. An **agent** is one durable conversation with its config, tool calls, and history. **Profiles** provide reusable agent configurations. 4. You send **inputs**, stream **events**, and resolve **interactions** in an agent. The [concepts](/concepts) page gives a short overview of this model. ## Start here > [card] [Quickstart](/quickstart) > > Write a config, create the grants, and launch an agent with the dashboard, the API, or the CLI > [card] [Concepts](/concepts) > > The object model, the agent loop, and access control # Connect a machine Source: https://docs.omnara.com/machines/connect Register your own machine and grant it to projects Connecting a BYO machine is three moves: create the machine record, run a daemon on the computer, and grant the machine to the projects that should use it. The dashboard's **Connect a machine** dialog, the CLI's `omnara machines create-local`, and the API's connect endpoint each do the first two in one step, and can attach project grants at the same time. ## Create the machine record A machine starts as a record in the control plane — the daemon attaches to it later using a **machine token**. The token is shown only once, so save the complete value and treat it as opaque. > [tab] Dashboard > > With **All projects** selected in the project switcher, open **Machines** and click **Connect machine**. In the **Connect a machine** dialog, set the machine name — this is what agent configs reference as `machine_name`, so pick something stable — and optionally pick projects under **Share with**. Click **Connect machine**; the next screen shows the machine token alongside the install command for the daemon install step below. > [tab] CLI > > To connect the computer you are sitting at, one command creates the machine, mints its token, and runs the installer locally: > > ```bash theme={null} > npx omnara machines create-local --display-name build-server-1 --project-ids proj_7mv4qtrwz3jehcyd5n6a2bfgik > ``` > > It refuses to run if `~/.omnarad` (or `$OMNARA_HOME`) already holds a daemon install; run `omnarad uninstall` first to connect the computer as a new machine. If the installer fails after the machine was created, the command prints the token and the install command so you can finish by hand. > > To connect a different computer, create the machine from anywhere and carry the output over: > > ```bash theme={null} > npx omnara machines create --display-name build-server-1 --project-ids proj_7mv4qtrwz3jehcyd5n6a2bfgik > ``` > > The output includes the one-time `token` plus an `install_command` to run on the target machine. Pass `--token-name` to label the daemon token (it defaults to `daemon`), and `--cwd`, `--env`, or `--secret-env` to set execution defaults. > > `--project-ids` takes an ID from `npx omnara projects list`; repeat it for each project, or leave it off and [grant the machine later](#grant-the-machine-to-a-project). > [tab] API > > The examples assume the client, `$ORG`/`orgID`, and `$PROJ`/`projectID` setup from the [quickstart](/quickstart). > > ```bash CLI theme={null} > npx omnara machines create \ > --display-name build-server-1 \ > --description "Bare-metal builder in the office rack" \ > --cwd /srv/repo \ > --env '{"CI": "true"}' \ > --secret-env '{"GITHUB_TOKEN": "sec_7jehcyd5n6a2bfgik7mv4qtrwz"}' \ > --project-ids proj_7mv4qtrwz3jehcyd5n6a2bfgik \ > --json > ``` > > ```bash REST theme={null} > curl "$OMNARA_API/orgs/$ORG/machines/connect" \ > -H "Authorization: Bearer $OMNARA_TOKEN" \ > -H "Content-Type: application/json" \ > -d '{ > "display_name": "build-server-1", > "description": "Bare-metal builder in the office rack", > "cwd": "/srv/repo", > "env": { "CI": "true" }, > "secret_env": { "GITHUB_TOKEN": "sec_7jehcyd5n6a2bfgik7mv4qtrwz" }, > "project_ids": ["'"$PROJ"'"] > }' > ``` > > ```typescript SDK theme={null} > const connection = await sdk.connectByoMachine({ > client, > path: { orgID }, > body: { > display_name: 'build-server-1', > description: 'Bare-metal builder in the office rack', > cwd: '/srv/repo', > env: { CI: 'true' }, > secret_env: { GITHUB_TOKEN: 'sec_7jehcyd5n6a2bfgik7mv4qtrwz' }, > project_ids: [projectID], > }, > }) > const machineID = connection.data.machine.id > const daemonToken = connection.data.token > ``` > > ```json theme={null} > { > "machine": { > "id": "mch_5n6a2bfgik7mv4qtrwz3jehcyd", > "org_id": "org_k7mv4qtrwz3jehcyd5n6a2bfgi", > "source_kind": "byo", > "display_name": "build-server-1", > "lifecycle_state": "active", > "connection_state": "offline", > "cwd": "/srv/repo", > "env": { "CI": "true" }, > "secret_env": { "GITHUB_TOKEN": "sec_7jehcyd5n6a2bfgik7mv4qtrwz" }, > "...": "..." > }, > "token": "omnara_daemon_v1_...", > "token_record": { > "id": "mdt_3jehcyd5n6a2bfgik7mv4qtrwz", > "machine_id": "mch_5n6a2bfgik7mv4qtrwz3jehcyd", > "name": "daemon", > "...": "..." > }, > "project_grants": [ > { > "id": "pmg_6a2bfgik7mv4qtrwz3jehcyd5n", > "machine_id": "mch_5n6a2bfgik7mv4qtrwz3jehcyd", > "source_kind": "explicit", > "...": "..." > } > ] > } > ``` > > The response includes the plaintext `token` and its metadata in `token_record`. Store the token now; later API responses return only its metadata. `project_ids` is optional (in the CLI, repeat `--project-ids` for each project); omit it to grant the machine later. To create a record without a token, use the [create machine](/api-reference/endpoints/machines/create-machine) endpoint instead and mint tokens separately: > > ```bash REST theme={null} > curl -X POST "$OMNARA_API/orgs/$ORG/machines/mch_5n6a2bfgik7mv4qtrwz3jehcyd/daemon-tokens" \ > -H "Authorization: Bearer $OMNARA_TOKEN" \ > -H "Content-Type: application/json" \ > -d '{"name": "build-server-1"}' > ``` > > ```typescript SDK theme={null} > const daemonToken = await sdk.createByoMachineDaemonToken({ > client, > path: { orgID, machineID: 'mch_5n6a2bfgik7mv4qtrwz3jehcyd' }, > body: { name: 'build-server-1' }, > }) > ``` `display_name` is what agent configs reference as `machine_name`, so pick something stable. `cwd`, `env`, and `secret_env` are execution defaults for every process on this machine; `secret_env` injects [secret](/organization/secrets) values by `sec_…` reference at process launch, so credentials never sit in the machine record. ## Install the daemon `omnara machines create-local` runs this step for you. Otherwise, on the target computer, run the installer and paste the machine token when prompted: ```sh theme={null} curl -fsSL https://api.omnara.com/install/omnarad.sh | sh ``` The installer supports macOS and Linux on amd64 and arm64. It installs `omnarad` under `~/.omnarad` and registers it as a user service using systemd on Linux or launchd on macOS. When no launchd or systemd user service manager is available, `omnarad` runs in the foreground instead; press Ctrl-C to stop it and run `omnarad start` to launch it again. The daemon connects outbound and updates itself. Manage it with `omnarad start|stop|restart|status`, or remove it with `omnarad uninstall`. Once the daemon connects, the machine appears online. ## Grant the machine to a project Machines are org-level; a project's agents can only target machines granted to that project. > [tab] Dashboard > > If you didn't share the machine while connecting it, select **All projects** in the project switcher, open **Machines**, and click **BYO Machines**. Open the machine's row actions, click **Share with project**, and select the project. > > From a project, open its **Machines** page and click **Share machines**. To see existing shares, click **BYO Machines**; open a row's actions and click **Stop sharing** to revoke one. > [tab] API > > If you didn't pass `project_ids` while connecting the machine, grant it afterwards: > > ```bash CLI theme={null} > npx omnara grant machines add --machine-id mch_5n6a2bfgik7mv4qtrwz3jehcyd --json > ``` > > ```bash REST theme={null} > curl "$OMNARA_API/orgs/$ORG/projects/$PROJ/machine-grants" \ > -H "Authorization: Bearer $OMNARA_TOKEN" \ > -H "Content-Type: application/json" \ > -d '{"machine_id": "mch_5n6a2bfgik7mv4qtrwz3jehcyd"}' > ``` > > ```typescript SDK theme={null} > await sdk.createProjectMachineGrant({ > client, > path: { orgID, projectID }, > body: { machine_id: 'mch_5n6a2bfgik7mv4qtrwz3jehcyd' }, > }) > ``` > > ```json theme={null} > { > "grant": { > "id": "pmg_6a2bfgik7mv4qtrwz3jehcyd5n", > "machine_id": "mch_5n6a2bfgik7mv4qtrwz3jehcyd", > "source_kind": "explicit", > "...": "..." > }, > "...": "..." > } > ``` > > List the project's explicit machine grants with `omnara grant machines list` or the [list endpoint](/api-reference/endpoints/machines/list-project-machine-grants), and revoke one by grant ID with `omnara grant machines delete {grant-id}` or the delete endpoint. > > Revoking the grant stops that project's work on the machine but leaves the machine itself untouched. Grants created through a [pool grant](/machines/pools) are managed through the pool and cannot be revoked individually. With the grant in place, configs in that project can reference the machine: ```yaml theme={null} machine_sources: - machine_name: build-server-1 cwd: /srv/repo ``` ## Manage a machine The dashboard's **Machines** page (with **All projects** selected in the project switcher) shows every machine you can access. `omnara machines list` and the org API provide the same view and explain whether access comes from your org role, having created the machine, or a project grant. The project API lists only machines that project's agents can use. You can update `cwd`, `env`, and `secret_env` with `omnara machines update {machine-id}` or through the API; machine names cannot be changed. Machine tokens can be created, listed, and revoked through the API. Deleting a BYO machine disconnects its daemon, revokes its tokens, removes its grants, and releases its agent bindings. In the dashboard, open the machine's row menu and click **Delete**; in the CLI, run `omnara machines delete {machine-id}`. > [!NOTE] > > Full schema and playground: [Connect a BYO machine](/api-reference/endpoints/machines/connect-a-byo-machine) · [Create machine](/api-reference/endpoints/machines/create-machine) · [List visible machines](/api-reference/endpoints/machines/list-visible-machines) · [Daemon tokens](/api-reference/endpoints/machines/list-byo-machine-daemon-tokens) · [Machine grants](/api-reference/endpoints/machines/create-project-machine-grant). # Machines overview Source: https://docs.omnara.com/machines/overview How agents get computers — BYO machines, pools, grants, and bindings A machine is an environment where agents run tools such as `run_command`. It can be a laptop on your desk, a VM in your VPC, a cloud sandbox created on demand or any other environment with access to a shell. Every machine runs the same small daemon, `omnarad`, which connects outbound to the control plane. You do not need to open inbound ports, distribute SSH keys, or give Omnara a network path into your infrastructure. Most importantly, **the machine does not own the agent**. Agents live in the control plane as [event logs](/concepts); machines are execution environments they can attach and release. Disconnecting a machine does not kill the agent. ## Two ways to get one | | BYO (bring your own) | Pool | | - | - | - | | What it is | A computer you already have, registered by installing the daemon | Capacity provisioned on demand from Agent37, Arker, Blaxel, boxd, CreateOS, Daytona, E2B, Freestyle, Modal, Superserve, Tenki, or Unikraft Cloud | | Created by | You, via the [install flow](/machines/connect) | Omnara, when an agent needs one | | Lifetime | Yours to manage | Created on demand — at launch or mid-conversation — and deleted when released or after a configured idle period | | Best for | Access to things only your infra can reach — mounts, internal networks, GPUs | Disposable, scale-to-zero execution | | `source_kind` | `byo` | `pool` | The [mounted storage example](/examples/mounted-storage) is the canonical BYO story. ## Machine state Every machine reports two independent states: * **`lifecycle_state`** tracks whether the machine is being provisioned, active, being deleted, or deleted. It also reports provisioning and deletion failures. * **`connection_state`** reports whether the machine is currently `online`, `asleep`, or `offline`. An `active` machine can be `offline`, for example when someone closes a laptop. New commands fail with a retryable error. A process may continue on the machine, but the agent cannot interact with it until the daemon reconnects. After a grace period, tool calls waiting on that process return a retryable `machine_unreachable` result. A machine is available to an agent only when its binding is attached, its project grant still exists, its lifecycle is `active`, and its daemon is `online` or the machine is asleep and can be woken. The [`inspect_machine`](/tools/built-in#list_machines--inspect_machine) tool reports this as `executable`. ## Who may use a machine Machines and pools are **org-level** resources; projects get access through explicit grants: ```mermaid theme={null} graph LR M["Machine (BYO)"] -->|machine grant| P["Project"] POOL["Machine pool"] -->|pool grant| P P --> CFG["Agent config references by name"] CFG --> B["Binding at launch"] ``` A [machine grant](/machines/connect#grant-the-machine-to-a-project) lets a project's agents use one specific machine. A [pool grant](/machines/pools#grant-a-pool-to-a-project) lets them create machines from a pool, optionally with tighter limits and project-specific environment variables. Agent configs reference granted machines and pools by name in `machine_sources`. Omnara checks access when the config is created and again when an agent launches. ## Bindings: how an agent holds a machine At launch, each BYO machine and each pool machine requested through `initial_num_machines` becomes an **agent machine binding**. Agents can create more pool machines later with [`create_machine`](/tools/built-in#create_machine). A binding stores the working directory and environment for that agent. Tools select the machine using its public `machine_id` (`mch_...`), the same ID used by the API. Agents sharing a machine use the same machine ID: ```json theme={null} "machine_bindings": [ { "machine_id": "mch_aaaaaaaaaaaaaaaaaaaaaaaaae", "binding_kind": "pool", "state": "attached", "cwd": "/workspace", "...": "..." } ] ``` Bindings remain `attached` until released. Releasing a pool binding schedules its machine for deletion; releasing a BYO binding only detaches it from the agent. Archiving an agent releases all of its bindings. The [command and process tools](/tools/built-in#commands) use these bindings. ## Reading order > [card] [Connect a machine](/machines/connect) > > Register a BYO machine and grant it to a project > [card] [Machine pools](/machines/pools) > > On-demand capacity from Agent37, Arker, Blaxel, boxd, CreateOS, Daytona, E2B, Freestyle, Modal, Superserve, Tenki, or Unikraft > [card] [Built-in tools](/tools/built-in#commands) > > The command and process tools, from the agent's point of view > [card] [Mounted storage example](/examples/mounted-storage) > > A complete BYO walkthrough # Machine pools Source: https://docs.omnara.com/machines/pools On-demand machine capacity from Agent37, Arker, Blaxel, boxd, CreateOS, Daytona, E2B, Freestyle, Modal, Superserve, Tenki, or Unikraft Cloud A machine pool lets Omnara create machines from a sandbox provider when an agent needs them. Omnara gives you two ways to run pool machines: * **Omnara-managed pools** — ready-to-use sandboxes. Omnara manages the sandbox provider account, credentials, and [default machine environment](#default-machine-environment). * **Bring your own provider** — connect your Agent37, Arker, Blaxel, boxd, CreateOS, Daytona, E2B, Freestyle, Modal, Superserve, Tenki, or Unikraft Cloud account and create a custom pool. You provide the API credential and choose the image or snapshot containing your tools and dependencies. See [Sandbox providers](/machines/providers/overview) for each provider. ## Default machine environment Machines from Omnara-managed pools run Ubuntu 26.04 as root, with common development tools preinstalled so agents can start working without installing them. The default working directory is `/workspace`. Custom pools and self-hosted deployments use the image or snapshot you choose. | Category | Included | | - | - | | Languages | Python 3.14, Node.js 24, Go 1.27, C and C++ (gcc 15) | | Package managers | pip, uv, npm, pnpm, Yarn | | Build tools | make, cmake, ninja | | Databases | PostgreSQL 18, Redis 8, SQLite | | Command-line tools | `git`, `gh`, `jq`, `yq`, `rg`, `curl`, `wget`, `zip`, `sudo` | | Python libraries | numpy, pandas, matplotlib, openpyxl, python-docx, python-pptx, pypdf | | PDF tools | `pdftotext`, `pdftoppm`, `qpdf` | | Browser automation | Playwright for Python with Chromium headless shell | * `/bin/sh` is bash, so [`run_command`](/tools/built-in#run_command) accepts bash syntax with the default shell. * `pip install` and `uv pip install --system` install into the system Python without a virtual environment. * PostgreSQL and Redis are installed but not running. Create and start a PostgreSQL cluster with `pg_createcluster 18 main --start`. * A `GITHUB_TOKEN` or `GH_TOKEN` [environment variable](/agents/configuration#machines) authenticates both `gh` and `git` to GitHub over HTTPS. Agents can install other tools with `apt-get` or a language's package manager. ## What persists A pool machine keeps its files, installed dependencies, and running processes across turns, including when the provider suspends and resumes it while idle. Omnara deletes a pool machine when: * The agent calls [`delete_machine`](/tools/built-in#delete_machine). * The agent's config no longer includes that pool source. * The agent is archived. * The pool is deleted. * Its configured idle-deletion period elapses. * It's in an Omnara-managed pool and its organization stays out of credits. Idle deletion waits until the machine has no queued or active process work. Process activity restarts the idle period. A replacement starts from the pool's original image or snapshot. The agent's conversation, event history, and uploaded artifacts remain in Omnara after the machine is deleted. ## Runtime protection Runtime protection is an optional pool setting that defaults to disabled. When enabled, Omnara may delete a provider sandbox that remains running after its daemon becomes inactive. Out-of-band work that keeps a sandbox running without its daemon is therefore not supported on a protected pool. Agent37, Arker, Blaxel, boxd, CreateOS, Daytona, E2B, Freestyle, Modal, Superserve, Tenki, and Unikraft pools support runtime protection. It does not apply to directly connected machines, and provider errors delay enforcement. The setting belongs to the pool and cannot be overridden by a project grant or agent config. ## Create a pool > [!NOTE] > > New organizations already have default pools granted to their first project. Check the **Machines** page, with **All projects** selected in the project switcher, before creating one. Self-hosted deployments configure default pools separately. For a custom pool, store the provider's API key as an org-owned [secret](/organization/secrets) first. > [tab] API > > The examples assume the client, `$ORG`/`orgID`, and `$PROJ`/`projectID` setup from the [quickstart](/quickstart). > > A Blaxel pool: > > ```bash CLI theme={null} > npx omnara pools create \ > --name sandbox-pool \ > --provider blaxel \ > --provider-auth-secret-id sec_hcyd5n6a2bfgik7mv4qtrwz3je \ > --provider-config '{"workspace": "acme-main"}' \ > --default-machine-provider-options '{"image": "sandbox/repo-agent:latest", "region": "us-pdx-1"}' \ > --default-machine-memory-mb 1024 \ > --default-cwd /workspace \ > --max-total-machines 10 \ > --max-total-memory-mb 10240 \ > --min-machine-memory-mb 1024 \ > --max-machine-memory-mb 1024 \ > --json > ``` > > ```bash REST theme={null} > curl "$OMNARA_API/orgs/$ORG/machine-pools" \ > -H "Authorization: Bearer $OMNARA_TOKEN" \ > -H "Content-Type: application/json" \ > -d '{ > "name": "sandbox-pool", > "provider": "blaxel", > "provider_auth_secret_id": "sec_hcyd5n6a2bfgik7mv4qtrwz3je", > "provider_config": { "workspace": "acme-main" }, > "default_machine_provider_options": { "image": "sandbox/repo-agent:latest", "region": "us-pdx-1" }, > "default_machine_memory_mb": 1024, > "default_cwd": "/workspace", > "max_total_machines": 10, > "max_total_memory_mb": 10240, > "min_machine_memory_mb": 1024, > "max_machine_memory_mb": 1024 > }' > ``` > > ```typescript SDK theme={null} > const pool = await sdk.createMachinePool({ > client, > path: { orgID }, > body: { > name: 'sandbox-pool', > provider: 'blaxel', > provider_auth_secret_id: 'sec_hcyd5n6a2bfgik7mv4qtrwz3je', > provider_config: { workspace: 'acme-main' }, > default_machine_provider_options: { image: 'sandbox/repo-agent:latest', region: 'us-pdx-1' }, > default_machine_memory_mb: 1024, > default_cwd: '/workspace', > max_total_machines: 10, > max_total_memory_mb: 10240, > min_machine_memory_mb: 1024, > max_machine_memory_mb: 1024, > }, > }) > ``` > > ```json theme={null} > { > "id": "mpo_a2bfgik7mv4qtrwz3jehcyd5n6", > "name": "sandbox-pool", > "provider": "blaxel", > "default_machine_memory_mb": 1024, > "default_machine_provider_options": { "image": "sandbox/repo-agent:latest", "region": "us-pdx-1" }, > "max_total_machines": 10, > "max_total_memory_mb": 10240, > "min_machine_memory_mb": 1024, > "max_machine_memory_mb": 1024, > "...": "..." > } > ``` > [tab] Dashboard > > 1. With **All projects** selected in the project switcher, open **Machines**, click **New pool**, and choose Agent37, Arker, Blaxel, boxd, CreateOS, Daytona, E2B, Freestyle, Modal, Superserve, Tenki, or Unikraft. > 2. Name the pool, enter its workspace if the provider needs one, and select its provider credential secret. Use **New secret** to create one without leaving the dialog. The dialog then checks that the provider accepts it. > 3. Enter the image or snapshot, provider settings, machine size, and capacity limits. You can also set a startup script, working directory, and environment variables. > 4. Optionally pick projects under **Share with**, then create it. > > After creation, use the pool's row to edit, share, or delete it. Every pool requires `max_total_machines`. Resource requirements then depend on the provider; see [Sandbox providers](/machines/providers/overview). ## Grant a pool to a project Pools belong to the organization. A project needs a grant to use one. The grant can use the pool defaults or set stricter capacity limits and project-specific environment variables. > [tab] API > > ```bash CLI theme={null} > npx omnara grant pools add \ > --machine-pool-id mpo_a2bfgik7mv4qtrwz3jehcyd5n6 \ > --max-total-machines 3 \ > --default-machine-env-overlay '{"DEPLOY_ENV": "staging"}' \ > --default-machine-secret-env-overlay '{"NPM_TOKEN": "sec_7jehcyd5n6a2bfgik7mv4qtrwz"}' \ > --json > ``` > > ```bash REST theme={null} > curl "$OMNARA_API/orgs/$ORG/projects/$PROJ/machine-pool-grants" \ > -H "Authorization: Bearer $OMNARA_TOKEN" \ > -H "Content-Type: application/json" \ > -d '{ > "machine_pool_id": "mpo_a2bfgik7mv4qtrwz3jehcyd5n6", > "max_total_machines": 3, > "default_machine_env_overlay": { "DEPLOY_ENV": "staging" }, > "default_machine_secret_env_overlay": { "NPM_TOKEN": "sec_7jehcyd5n6a2bfgik7mv4qtrwz" } > }' > ``` > > ```typescript SDK theme={null} > const grant = await sdk.createProjectMachinePoolGrant({ > client, > path: { orgID, projectID }, > body: { > machine_pool_id: 'mpo_a2bfgik7mv4qtrwz3jehcyd5n6', > max_total_machines: 3, > default_machine_env_overlay: { DEPLOY_ENV: 'staging' }, > default_machine_secret_env_overlay: { NPM_TOKEN: 'sec_7jehcyd5n6a2bfgik7mv4qtrwz' }, > }, > }) > ``` > > ```json theme={null} > { > "id": "pmpg_2bfgik7mv4qtrwz3jehcyd5n6a", > "machine_pool_id": "mpo_a2bfgik7mv4qtrwz3jehcyd5n6", > "max_total_machines": 3, > "default_machine_env_overlay": { "DEPLOY_ENV": "staging" }, > "default_machine_secret_env_overlay": { "NPM_TOKEN": "sec_7jehcyd5n6a2bfgik7mv4qtrwz" }, > "...": "..." > } > ``` > > To manage existing grants, list the project's pool grants (`omnara grant pools list`), change a grant's overrides (`omnara grant pools edit {pool-grant-id}`), or revoke one (`omnara grant pools delete {pool-grant-id}`); the REST API and SDK expose the same list, update, and delete operations on `machine-pool-grants`. > [tab] Dashboard > > On the pool's row on the organization **Machines** page, click **Share with project** and select the project. Open **Overrides** to set optional project limits and environment variables. > > You can also open the project's **Machines** page and click **Share pool**. The page's **Shared machine pools** table lists existing shares; click **Stop sharing** to revoke one. When Omnara creates a pool machine, it combines the machine size, working directory, environment variables, secret environment variables, and provider options from three places: 1. The pool provides the defaults. 2. The project grant can narrow or override those defaults. 3. The agent config can customize them for that agent. Later values replace earlier ones. A project grant or agent config can remove an environment variable inherited from the pool by setting it to `null`. Pool and project limits remain hard constraints. `delete_after_idle_minutes` follows the same override order. An unset grant or agent value inherits, `0` disables idle deletion at that level, and enabled values must be at least 5 minutes. ## Use it from a config ```yaml theme={null} machine_sources: - machine_pool_name: sandbox-pool cwd: /workspace/my-repo machine_memory_mb: 1024 # within pool limits max_machines: 3 # this agent's own ceiling ``` By default, Omnara creates one machine at launch and allows one machine in total. Use `initial_num_machines` to control how many are created at launch and `max_machines` to set the total limit. The agent can call [`create_machine`](/tools/built-in#create_machine) until it reaches that limit. `machine_cpu` and `machine_memory_mb` set the default size; where supported, the agent can override `cpu` and `memory_mb` per `create_machine` call within pool and project limits. > [!NOTE] > > Full schema and playground: [Create machine pool](/api-reference/endpoints/machine-pools/create-machine-pool) · [Update machine pool](/api-reference/endpoints/machine-pools/update-machine-pool) · [Pool grants](/api-reference/endpoints/machine-pools/create-project-machine-pool-grant). # Agent37 Source: https://docs.omnara.com/machines/providers/agent37 Always-on Linux computers for agents, running in seconds from any image and billed by the minute [Agent37](https://www.agent37.com) gives each machine its own always-on Linux sandbox with a persistent disk, created in a few seconds. Connect your Agent37 workspace with a custom pool. ## Why Agent37 * **Omnara's tools, baked in.** `agent37-omnara` is Ubuntu 24.04 with the same development tools as Omnara's [default machine environment](/machines/pools#default-machine-environment), so a pool only needs an API key. For your own stack, turn a public registry image into a template with one API call, or build one from a Dockerfile in Agent37's cloud with no local Docker. * **A URL for every port.** A dev server or app an agent starts gets its own HTTPS address at `https://{instance}-{port}.agent37.app`, and a [signed link](https://www.agent37.com/docs/agents-api/urls) opens it in your browser. * **SSH into any machine.** Add your public key once, then use `ssh`, `scp`, or VS Code Remote to watch an agent work or pick up where it left off. See [SSH](https://www.agent37.com/docs/agents-api/ssh). * **Pay for the minutes you use.** Machines bill by the minute from a prepaid balance, and auto top-up refills it before it runs out. ## Set up an Agent37 pool **1. Create an API key** In the [Agent37 dashboard](https://www.agent37.com/dashboard/cloud/api-keys), create an API key and store it as an org-owned [secret](/organization/secrets) in Omnara. Machines run and bill in that key's workspace, so [add funds](https://www.agent37.com/docs/agents-api/billing) before you create the pool: a workspace that has never been topped up runs one small machine. **2. Choose a template** Leave the template empty to use `agent37-omnara`: Ubuntu 24.04 as root with the [default machine environment](/machines/pools#default-machine-environment) preinstalled: Python, Node.js, Go, PostgreSQL, Redis, `gh`, `rg`, PDF tools, and Playwright with headless Chromium. For your own tools, use a template in your Agent37 workspace. Register a public `linux/amd64` image that meets the [custom image requirements](/machines/providers/overview#custom-image-requirements) with one call: ```bash theme={null} curl -X POST https://api.agent37.com/v1/templates \ -H "Authorization: Bearer $AGENT37_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "omnara-machine", "image_ref": "docker.io/library/buildpack-deps:24.04-curl", "entrypoint": ["/bin/sh", "-c", "trap \"exit 0\" TERM; while :; do sleep 3600 & wait $!; done"] }' ``` * Omnara starts the daemon with a command on the running machine, so the image's main process only has to keep running. The `entrypoint` above does that for an image whose own command exits, and cleans up the processes agents leave behind. * Agents run commands as the image's default user, so pick an image that runs as root if they should install packages. * Machines are containers, so pick an image that doesn't need Docker or systemd. * To build one from a Dockerfile, use `npx agent37 templates build`. See [Agent37 templates](https://www.agent37.com/docs/agents-api/templates). **3. Create the pool** On the **Machines** page, click **New pool** and choose **Agent37**. Enter the credential, the template (or leave it empty), the CPU and memory, and capacity limits. Each machine is 2 vCPU with 4 GB of memory, or 4 vCPU with 8, 16, or 32 GB. Only environment variables whose names use letters, digits, and underscores, and don't start with a digit, reach the machine. To use the API instead, call [Create a pool](/machines/pools#create-a-pool) with the fields under [API fields](#api-fields). ## API fields When you create a pool through the API, send these fields in the [Create a pool](/machines/pools#create-a-pool) request. `provider_config` applies to the whole pool, and `default_machine_provider_options` sets the options for each new machine, which project grants and agent configs can override. For example: ```jsonc theme={null} { "name": "agent37-pool", "provider": "agent37", "provider_auth_secret_id": "sec_hcyd5n6a2bfgik7mv4qtrwz3je", "default_machine_provider_options": { "template": "omnara-machine" // optional }, "default_machine_cpu": 2, "default_machine_memory_mb": 4096, "max_machine_cpu": 4, "max_machine_memory_mb": 8192, "max_total_cpu": 20, "max_total_memory_mb": 40960, "max_total_machines": 10 } ``` * `template` is optional. Omit it to use `agent37-omnara`, or set the name of a template in your Agent37 workspace. Add `@`, such as `omnara-machine@2`, to pin a published revision. * CPU and memory must be one of Agent37's sizes: 2 vCPU with 4096 MB, or 4 vCPU with 8192, 16384, or 32768 MB. * Optional: `startup_script` in `default_machine_provider_options` runs on each new machine before the daemon starts, and `allowed_templates` in `provider_config` is an [allowlist](/machines/providers/overview#allowlists). # Arker Source: https://docs.omnara.com/machines/providers/arker Hyper-elastic, durable virtual machines for agents at extreme scale [Arker](https://arker.ai) runs hyper-elastic, durable virtual machines for agents at extreme scale, redesigned at every layer to maximize agent performance per dollar. Every machine is a fork of a source VM you choose, either your own or one of Arker's public VMs. Connect your Arker organization with a custom pool. ## Why Arker * **Faithful forks.** Each machine forks the source VM's full state, disk and memory included, so it starts with your tools installed and your services already running. * **Hyper-elastic.** Arker's custom hypervisor spins machines up, down, and resizes them in milliseconds. * **Durable, always on.** Arker transparently snapshots machines and restores them on another host if one fails, backed by a 99.9% uptime SLA. * **Sleep with your state intact.** With [sleep](/machines/providers/overview#sleep-when-idle) on, Arker suspends idle machines and restores their memory, files, and running processes in milliseconds, even after hours unused. * **Run next to your data.** Place machines on AWS, Google Cloud, or Arker's own bare metal, in the same data centers where your backend and data live. ## Set up an Arker pool **1. Create an API key** In the [Arker console](https://arker.ai/console/settings/api-keys), create an API key. Store the key as an org-owned [secret](/organization/secrets) in Omnara. **2. Choose or build a source VM** Arker's public `ubuntu-base` VM works as is. To add your own tools, fork it into a named VM in the region your pool will use, install what you need, and use that name as the source. With the [Arker CLI](https://www.npmjs.com/package/@arker-ai/cli): ```bash theme={null} npm install -g @arker-ai/cli export ARKER_API_KEY=ark_live_... ARKER_PROVIDER=aws ARKER_REGION=us-west-2 arker fork ubuntu-base --name my-agent-box arker run my-agent-box 'apt-get update && apt-get install -y ripgrep' ``` * Omnara sets each machine's CPU and memory to the requested size. The disk keeps the source VM's size. * Don't use a VM that has run the Omnara daemon as the source, such as a machine from an Omnara pool. Machines created from it keep the old machine's identity and credentials and never come online. **3. Create the pool** On the **Machines** page, click **New pool** and choose **Arker**. Enter the credential, source VM, region, CPU and memory, and capacity limits. Only environment variables whose names use letters, digits, and underscores, and don't start with a digit, reach the startup script. To use the API instead, call [Create a pool](/machines/pools#create-a-pool) with the fields under [API fields](#api-fields). The dashboard can't turn on sleep yet, so set `sleep_after_ms` through the API. ## API fields When you create a pool through the API, send these fields in the [Create a pool](/machines/pools#create-a-pool) request. `provider_config` applies to the whole pool, and `default_machine_provider_options` sets the options for each new machine, which project grants and agent configs can override. For example: ```jsonc theme={null} { "name": "arker-pool", "provider": "arker", "provider_auth_secret_id": "sec_hcyd5n6a2bfgik7mv4qtrwz3je", "default_machine_provider_options": { "source": "ubuntu-base", "region": "aws-us-west-2", "sleep_after_ms": 300000 // optional }, "default_machine_cpu": 2, "default_machine_memory_mb": 4096, "max_machine_cpu": 4, "max_machine_memory_mb": 5120, "max_total_cpu": 40, "max_total_memory_mb": 51200, "max_total_machines": 10 } ``` * `source` is the name of a VM in your Arker organization, or a public VM such as `ubuntu-base`. It must exist in the pool's region. * `region` is where machines run, written as it appears in the Arker API endpoint, such as `aws-us-west-2` or `arker-eu`. See [Arker's regions](https://arker.ai/docs/regions). * Keep the maximums within the source VM's limits. `ubuntu-base` allows up to 4 vCPU and 5120 MB of memory. * `sleep_after_ms` turns on [sleep](/machines/providers/overview#sleep-when-idle). Omit it to keep machines awake. * Optional: `startup_script` in `default_machine_provider_options` runs on each new machine before the daemon starts. In `provider_config`, `allowed_sources` and `allowed_regions` are [allowlists](/machines/providers/overview#allowlists), and `api_base_url` replaces the region's API endpoint, such as for a private Arker region. # Blaxel Source: https://docs.omnara.com/machines/providers/blaxel Infrastructure for autonomous agents, with microVMs that resume in about 25 ms [Blaxel](https://blaxel.ai) is the infrastructure foundation for autonomous agents. Each machine is an isolated microVM that can sleep between turns and wake with its memory intact. On Omnara's hosted service, your organization's default pool runs on Blaxel and is managed by Omnara, so there's nothing to set up. You can also connect your own Blaxel workspace with a custom pool. ## Why Blaxel * **Hardware isolation.** Each machine runs in its own microVM with its own kernel, boots in milliseconds, and is fully isolated from every other machine. * **Resume in about 25 ms.** With [sleep](/machines/providers/overview#sleep-when-idle) on, idle machines stop using compute and come back in about 25 ms with their memory, files, and running processes intact, with no cold starts to manage. * **Files at memory speed.** Each machine's file system runs in memory, so installs, builds, and searches are fast. * **Bring your own Dockerfile.** Build images from any Dockerfile, including base images from private Docker Hub, GitHub, Google Artifact Registry, and Amazon ECR repositories. * **US and European regions.** Run machines in Oregon, Virginia, London, or Frankfurt. ## Set up a Blaxel pool **1. Create an API key** In the [Blaxel console](https://app.blaxel.ai), create a service account in your workspace settings and generate an API key for it. A personal key from **Profile > Security** also works, but it can access every workspace you belong to. Store the key as an org-owned [secret](/organization/secrets) in Omnara, and note your workspace **name** from the console URL (`app.blaxel.ai/{workspace}`). **2. Build an image** Every Blaxel image must include Blaxel's `sandbox-api` binary. Start from a Dockerfile that installs your agents' tools and meets the [custom image requirements](/machines/providers/overview#custom-image-requirements): ```dockerfile theme={null} FROM ubuntu:24.04 RUN apt-get update \ && apt-get install -y --no-install-recommends ca-certificates curl git \ && rm -rf /var/lib/apt/lists/* COPY --from=ghcr.io/blaxel-ai/sandbox:latest /sandbox-api /usr/local/bin/sandbox-api ENV HOME=/root ENTRYPOINT ["/usr/local/bin/sandbox-api"] ``` Add a `blaxel.toml` next to it. Blaxel slims images by default, which can remove compilers and package manager files, so turn that off: ```toml theme={null} type = "sandbox" name = "omnara-agent" [build] slim = false ``` Run `bl push` with the [Blaxel CLI](https://docs.blaxel.ai/cli-reference/introduction), then use `sandbox/omnara-agent:latest` or a pinned tag as the image. See Blaxel's [template docs](https://docs.blaxel.ai/Sandboxes/Templates) for private registries. **3. Create the pool** On the **Machines** page, click **New pool** and choose **Blaxel**. Enter the workspace name, credential, image, region, memory, and capacity limits. Blaxel keeps each machine's files in memory, so give each machine at least 4 GB for coding work. To use the API instead, call [Create a pool](/machines/pools#create-a-pool) with the fields under [API fields](#api-fields). The dashboard can't turn on sleep yet, so set `sleep_after_ms` through the API. ## API fields When you create a pool through the API, send these fields in the [Create a pool](/machines/pools#create-a-pool) request. `provider_config` applies to the whole pool, and `default_machine_provider_options` sets the options for each new machine, which project grants and agent configs can override. For example: ```jsonc theme={null} { "name": "blaxel-pool", "provider": "blaxel", "provider_auth_secret_id": "sec_hcyd5n6a2bfgik7mv4qtrwz3je", "provider_config": { "workspace": "acme-main" }, "default_machine_provider_options": { "image": "sandbox/omnara-agent:latest", "region": "us-pdx-1", "sleep_after_ms": 300000 // optional }, "default_machine_memory_mb": 4096, "max_machine_memory_mb": 8192, "max_total_memory_mb": 40960, "max_total_machines": 10 } ``` * `workspace` is your Blaxel workspace name. * `region` is `us-pdx-1`, `us-was-1`, `eu-lon-1`, or `eu-fra-1`. * `sleep_after_ms` turns on [sleep](/machines/providers/overview#sleep-when-idle). Omit it to keep machines awake. * Blaxel pools set memory only, so don't send any CPU fields. * Optional: `startup_script` in `default_machine_provider_options` runs on each new machine before the daemon starts, and `allowed_images` and `allowed_regions` in `provider_config` are [allowlists](/machines/providers/overview#allowlists). # boxd Source: https://docs.omnara.com/machines/providers/boxd Composable computers for devs and agents, with a full computer in under 10 ms [boxd](https://boxd.sh) makes composable computers for devs and agents: full, hardware-isolated Linux computers you compose once and spin up as many as you need. Each machine is a KVM microVM running Ubuntu 24.04 with its own kernel. Connect your boxd org with a custom pool. ## Why boxd * **A full computer in under 10 ms.** Machines boot from cold in under 10 ms, and Claude Code, Codex, and OpenCode ship on every one. * **Give the agent root. Walk away.** Every agent gets a real virtual machine with its own kernel: root, systemd, Docker, and no way to reach the host or the machine next door. Omnara creates each machine in boxd's isolated mode, which also cuts it off from the rest of your boxd account: no in-VM `boxd` CLI, integrations, or private network to your other machines. * **Persistent by default.** A boxd machine is the same machine tomorrow, with the repo, installed packages, warm caches, and whatever the agent left running still there. With [sleep](/machines/providers/overview#sleep-when-idle) on, idle machines suspend and resume in under a millisecond, running processes and all. * **Set up once, use infinitely.** Install your tools once and snapshot the machine, memory and running processes included. Every new machine starts with everything already set up and running. * **In Europe.** boxd runs in European data centers, which for a lot of teams is the difference between an agent being allowed near the codebase and not. ## Set up a boxd pool **1. Create an API key** In the [boxd console](https://boxd.sh/app), open **API keys** and create a key, or run `boxd auth keys create omnara`. Each key belongs to one boxd org, and Omnara creates machines in that org. Store the key as an org-owned [secret](/organization/secrets) in Omnara. **2. Choose a snapshot** Leave the snapshot empty to use boxd's base image. To add your own tools, set up a boxd machine the way you want it, then save it while it's running: ```bash theme={null} boxd snapshots save my-machine agent-box ``` * A snapshot captures the machine's memory and disk, and machines created from it keep the snapshot's size. * Don't snapshot a machine that has run the Omnara daemon. Machines created from it keep the old machine's identity and credentials and never come online. **3. Create the pool** On the **Machines** page, click **New pool** and choose **boxd**. Enter the credential, snapshot (or leave it empty), CPU and memory maximums, and capacity limits. Machines take the snapshot's size, or your boxd org's default size without a snapshot, so set the maximums to at least that size. An org admin sets the default with `boxd manage size set --vcpu 2`. Only environment variables whose names use letters, digits, and underscores, and don't start with a digit, reach the machine. To use the API instead, call [Create a pool](/machines/pools#create-a-pool) with the fields under [API fields](#api-fields). The dashboard can't turn on sleep yet, so set `sleep_after_ms` through the API. ## API fields When you create a pool through the API, send these fields in the [Create a pool](/machines/pools#create-a-pool) request. `provider_config` applies to the whole pool, and `default_machine_provider_options` sets the options for each new machine, which project grants and agent configs can override. For example: ```jsonc theme={null} { "name": "boxd-pool", "provider": "boxd", "provider_auth_secret_id": "sec_hcyd5n6a2bfgik7mv4qtrwz3je", "default_machine_provider_options": { "sleep_after_ms": 300000 // optional }, "max_machine_cpu": 2, "max_machine_memory_mb": 8192, "max_total_cpu": 20, "max_total_memory_mb": 81920, "max_total_machines": 10 } ``` * `snapshot` is optional. Omit it, as here, to use boxd's base image, or set the name of a saved snapshot. * The snapshot sets each machine's size, or your boxd org's default size without a snapshot, so the pool doesn't need CPU and memory defaults, and the maximums must be at least that size. * `sleep_after_ms` turns on [sleep](/machines/providers/overview#sleep-when-idle). Omit it to keep machines awake. `default_machine_provider_options` itself is required, so send it as `{}` when it's empty. * Optional: `startup_script` in `default_machine_provider_options` runs on each new machine before the daemon starts, and `allowed_snapshots` in `provider_config` is an [allowlist](/machines/providers/overview#allowlists). # CreateOS Source: https://docs.omnara.com/machines/providers/createos Give every agent its own computer, a Firecracker microVM that boots in about 30 ms [CreateOS Sandbox](https://createos.sh/products/sandbox) is a safe computer for AI agents. Each machine is a Firecracker microVM with its own kernel, root filesystem, and network identity: the isolation of a VM with the startup speed of a container. Connect your CreateOS account with a custom pool. ## Why CreateOS * **A real machine boundary, not a shared kernel.** Every machine runs its own Linux kernel, so code inside it can't see the host or other tenants, and AI-generated code runs isolated from your own infrastructure. * **Boots in about 30 ms.** CreateOS starts a full Linux VM in about 30 ms at p90. * **Pause when idle, resume in place.** With [sleep](/machines/providers/overview#sleep-when-idle) on, an idle machine pauses as a snapshot of its memory, processes, and disk. When an agent needs it again, it picks up exactly where it left off, with no cold boot and no setup to re-run. * **Your tools, preinstalled.** The `devbox:1` root filesystem is Ubuntu 24.04 with Python 3.12, pandas, NumPy, scikit-learn, and the OpenAI and Anthropic SDKs, plus Node.js 24, Bun, Go, Rust, and Docker. For your own stack, build a template from a Dockerfile once, and every machine launches with it, with no reprovisioning per run. ## Set up a CreateOS pool **1. Create an API key** In the [CreateOS dashboard](https://createos.sh/app/profile), open **Profile Settings**, go to **API Keys**, and create a key. Store the key as an org-owned [secret](/organization/secrets) in Omnara. **2. Choose a shape and root filesystem** The shape sets each machine's CPU and memory, such as `s-2vcpu-4gb` for 2 vCPU and 4 GiB. The shapes you can use depend on your CreateOS plan; see CreateOS's [limits](https://docs.createos.sh/Sandbox/Limits). The root filesystem is the image each machine boots from. `devbox:1` works as is. For your own tools, build a [template](https://docs.createos.sh/Sandbox/REST-API/Templates) from a Dockerfile that meets the [custom image requirements](/machines/providers/overview#custom-image-requirements), and use the template's name as the root filesystem. **3. Create the pool** On the **Machines** page, click **New pool** and choose **CreateOS**. Enter the credential, shape, root filesystem, CPU and memory maximums, and capacity limits. The maximums must be at least the shape's size. Only environment variables whose names use letters, digits, and underscores, and don't start with a digit, reach the startup script. CreateOS allows at most 64 variables per machine, including the ones Omnara adds, with up to 4 KiB each and 64 KiB in total, so the startup script can be at most 3,072 bytes. To use the API instead, call [Create a pool](/machines/pools#create-a-pool) with the fields under [API fields](#api-fields). The dashboard can't turn on sleep yet, so set `sleep_after_ms` through the API. ## API fields When you create a pool through the API, send these fields in the [Create a pool](/machines/pools#create-a-pool) request. `provider_config` applies to the whole pool, and `default_machine_provider_options` sets the options for each new machine, which project grants and agent configs can override. For example: ```jsonc theme={null} { "name": "createos-pool", "provider": "createos", "provider_auth_secret_id": "sec_hcyd5n6a2bfgik7mv4qtrwz3je", "default_machine_provider_options": { "shape": "s-2vcpu-4gb", "rootfs": "devbox:1", "sleep_after_ms": 300000 // optional }, "max_machine_cpu": 2, "max_machine_memory_mb": 4096, "max_total_cpu": 20, "max_total_memory_mb": 40960, "max_total_machines": 10 } ``` * `shape` is a CreateOS shape name. The shape sets each machine's size, so CPU and memory defaults are optional, and the maximums must be at least the shape's size. * `rootfs` is required. Use a built-in root filesystem such as `devbox:1`, or the name of one of your templates. * `sleep_after_ms` turns on [sleep](/machines/providers/overview#sleep-when-idle). Omit it to keep machines awake. * Optional: `startup_script` in `default_machine_provider_options` runs on each new machine before the daemon starts, and `allowed_shapes` and `allowed_rootfses` in `provider_config` are [allowlists](/machines/providers/overview#allowlists). # Daytona Source: https://docs.omnara.com/machines/providers/daytona Lightning-fast, isolated sandboxes for running AI-generated code [Daytona](https://www.daytona.io) is secure and elastic infrastructure for running AI-generated code. Connect your Daytona organization with a custom pool. ## Why Daytona * **Lightning-fast sandboxes.** Daytona creates a sandbox in under 90 ms, from code to execution. * **Isolated runtime protection.** Each machine is its own sandbox, separated from your infrastructure, so AI-generated code runs isolated from your systems. * **Massive parallelization.** Run many sandboxes side by side for concurrent agent workflows. * **Your existing Docker images.** Build snapshots from any tagged registry image or Dockerfile, including private images on Docker Hub, GitHub, Google Artifact Registry, and Amazon ECR. Start from a Docker-in-Docker image, and agents can run `docker compose` stacks right inside their machine. * **Work alongside your agents.** Open a web terminal from the Daytona dashboard, or connect over SSH from your own editor, to look at an agent's files and processes or lend a hand. ## Set up a Daytona pool **1. Create an API key** In the [Daytona dashboard](https://app.daytona.io/dashboard/keys), click **Create Key** and give it the `write:sandboxes` and `delete:sandboxes` permissions. Store the key as an org-owned [secret](/organization/secrets) in Omnara. **2. Choose or build a snapshot** Daytona's default `daytona-medium` snapshot (2 vCPU, 4 GiB) works as is. To build your own, start from an image with a pinned tag that meets the [custom image requirements](/machines/providers/overview#custom-image-requirements), and set its size: ```bash theme={null} daytona snapshot create omnara-agent --dockerfile ./Dockerfile --cpu 2 --memory 4 --disk 10 ``` Wait until the snapshot is **Active** in the region you plan to use. For private registry images, add the registry in the Daytona dashboard first. **3. Create the pool** On the **Machines** page, click **New pool** and choose **Daytona**. Enter the credential, snapshot name, target, CPU and memory maximums, and capacity limits. The maximums must be at least the snapshot's size. To use the API instead, call [Create a pool](/machines/pools#create-a-pool) with the fields under [API fields](#api-fields). ## API fields When you create a pool through the API, send these fields in the [Create a pool](/machines/pools#create-a-pool) request. `provider_config` applies to the whole pool, and `default_machine_provider_options` sets the options for each new machine, which project grants and agent configs can override. For example: ```jsonc theme={null} { "name": "daytona-pool", "provider": "daytona", "provider_auth_secret_id": "sec_hcyd5n6a2bfgik7mv4qtrwz3je", "default_machine_provider_options": { "snapshot": "daytona-medium", "target": "us" }, "max_machine_cpu": 2, "max_machine_memory_mb": 4096, "max_total_cpu": 20, "max_total_memory_mb": 40960, "max_total_machines": 10 } ``` * `snapshot` is the snapshot's name, not its ID. * `target` is `us` or `eu`. For a custom region, use its ID from the Daytona dashboard. * The snapshot sets each machine's size, so CPU and memory defaults are optional, and the maximums must be at least the snapshot's size. * Optional: `startup_script` in `default_machine_provider_options` runs on each new machine before the daemon starts. In `provider_config`, `api_base_url` defaults to `https://app.daytona.io/api`, and `allowed_snapshots` and `allowed_targets` are [allowlists](/machines/providers/overview#allowlists). # E2B Source: https://docs.omnara.com/machines/providers/e2b Give every agent a hardware-isolated Firecracker microVM that starts from a snapshot [E2B](https://e2b.dev) is the AI agent cloud: secure, on-demand machines for AI agents. Each machine is a Firecracker microVM that starts from a template, a snapshot with your tools already installed. Connect your E2B account with a custom pool. ## Why E2B * **A hardware-isolated microVM, not a shared-kernel container.** Every machine runs in its own Firecracker microVM, so AI-generated code stays isolated from your infrastructure and from other tenants. * **Starts from a snapshot.** A template is a saved, running machine, so each new machine starts in milliseconds with its tools installed and its processes already running. * **Agents ready to go.** Public templates come with Claude Code, Codex, OpenCode, Amp, and more installed. For your own stack, build a template from a Dockerfile once. * **Runs where you need it.** E2B runs in the US, EU, and Asia-Pacific, and can also run in your own cloud account or on your own machines with the same API. ## Set up an E2B pool **1. Create an API key** In the [E2B console](https://console.e2b.dev/?tab=keys), create an API key. Store the key as an org-owned [secret](/organization/secrets) in Omnara. **2. Choose a template** The template is the snapshot each machine starts from, and it sets each machine's CPU and memory. Use one of E2B's public templates, such as `code-interpreter-v1`, or one your team built. To build your own, write a Dockerfile that meets the [custom image requirements](/machines/providers/overview#custom-image-requirements) and run, for example, `e2b template create my-agent --cpu-count 4 --memory-mb 8192` to choose its size. See E2B's [template quickstart](https://docs.e2b.dev/template/quickstart). **3. Create the pool** On the **Machines** page, click **New pool** and choose **E2B**. Enter the credential, template, CPU and memory maximums, and capacity limits. The maximums must be at least the template's size. E2B ends each machine once it reaches your E2B plan's maximum continuous runtime, so put setup in the template or startup script rather than on the machine. To use the API instead, call [Create a pool](/machines/pools#create-a-pool) with the fields under [API fields](#api-fields). The dashboard can't set the domain, so to use another E2B region or E2B in your own cloud, set `domain` through the API. ## API fields When you create a pool through the API, send these fields in the [Create a pool](/machines/pools#create-a-pool) request. `provider_config` applies to the whole pool, and `default_machine_provider_options` sets the options for each new machine, which project grants and agent configs can override. For example: ```jsonc theme={null} { "name": "e2b-pool", "provider": "e2b", "provider_auth_secret_id": "sec_hcyd5n6a2bfgik7mv4qtrwz3je", "provider_config": { "domain": "e2b-juliett.dev" // optional }, "default_machine_provider_options": { "template": "code-interpreter-v1" }, "max_machine_cpu": 2, "max_machine_memory_mb": 2048, "max_total_cpu": 20, "max_total_memory_mb": 20480, "max_total_machines": 10 } ``` * `template` is a template ID, name, or `team/name`, without a tag. The template sets each machine's size, so CPU and memory defaults are optional, and the maximums must be at least the template's size. * `domain` is the E2B domain to use, such as `e2b-juliett.dev` for E2B's EU region. Omit it to use `e2b.app`. * Optional: `startup_script` in `default_machine_provider_options` runs on each new machine before the daemon starts, and `allowed_templates` in `provider_config` is an [allowlist](/machines/providers/overview#allowlists). # Freestyle Source: https://docs.omnara.com/machines/providers/freestyle Full Linux VMs for AI agents, ready in about 65 ms [Freestyle](https://www.freestyle.sh) gives agents real VMs: full Linux machines built for complex tasks that run for hours, days, or weeks. Each machine is an Ubuntu 24.04 VM with its own kernel. Connect your Freestyle account with a custom pool. ## Why Freestyle * **Instant startup.** A full Linux machine, ready in about 65 ms. * **Pause with your state intact.** With [sleep](/machines/providers/overview#sleep-when-idle) on, Freestyle pauses idle VMs and resumes them exactly where they left off, memory and running processes included. * **Power for your hardest workloads.** Every VM runs a full Linux kernel with FUSE and eBPF, plus systemd, and nested virtualization runs Docker and even other VMs inside it. Freestyle's Ubuntu snapshots come with Node.js, Python, Claude Code, Codex, and common developer tools preinstalled. * **Machines that start ready.** Snapshot an entire machine, memory and running processes included, and every new machine boots from that state. * **One machine for the whole job.** Freestyle sets no default limit on how long a VM runs, so files, packages, and build caches stay on the machine from one turn to the next. ## Set up a Freestyle pool **1. Create an API key** In the [Freestyle dashboard](https://dash.freestyle.sh), open **API tokens** and create a key, or run `npx freestyle@latest tokens create omnara`. Store the key as an org-owned [secret](/organization/secrets) in Omnara. **2. Choose or build a snapshot** Freestyle's `freestyle/ubuntu-sm` (2 vCPU, 4 GiB, 16 GB disk) and `freestyle/ubuntu` (4 vCPU, 8 GiB, 32 GB disk) work as is. To add your own tools, build a snapshot from an Ubuntu base snapshot with a setup script: ```bash theme={null} npx freestyle@latest snapshot create --base freestyle/ubuntu-sm --script ./setup.sh --slug my-agent-box ``` * Machines start at the snapshot's size, and Omnara grows CPU and memory to the requested size. The disk keeps the snapshot's size. * Don't snapshot a VM that has run the Omnara daemon. Machines created from it keep the old machine's identity and credentials and never come online. **3. Create the pool** On the **Machines** page, click **New pool** and choose **Freestyle**. Enter the credential, snapshot, CPU and memory, and capacity limits. Set the minimum CPU and memory to the snapshot's size, so agents can't request a machine smaller than the snapshot. To use the API instead, call [Create a pool](/machines/pools#create-a-pool) with the fields under [API fields](#api-fields). The dashboard can't turn on sleep yet, so set `sleep_after_ms` through the API. The daemon runs as a systemd service and comes back on its own after a reboot, and the startup script runs again whenever the service starts, so make it safe to rerun. For a self-hosted Omnara deployment, `OMNARA_PUBLIC_API_URL` must be reachable from the public internet, because Omnara only lets Freestyle machines reach public addresses. ## API fields When you create a pool through the API, send these fields in the [Create a pool](/machines/pools#create-a-pool) request. `provider_config` applies to the whole pool, and `default_machine_provider_options` sets the options for each new machine, which project grants and agent configs can override. For example: ```jsonc theme={null} { "name": "freestyle-pool", "provider": "freestyle", "provider_auth_secret_id": "sec_hcyd5n6a2bfgik7mv4qtrwz3je", "default_machine_provider_options": { "snapshot": "freestyle/ubuntu-sm", "sleep_after_ms": 300000 // optional }, "default_machine_cpu": 2, "default_machine_memory_mb": 4096, "min_machine_cpu": 2, // optional "min_machine_memory_mb": 4096, // optional "max_machine_cpu": 4, "max_machine_memory_mb": 8192, "max_total_cpu": 40, "max_total_memory_mb": 81920, "max_total_machines": 10 } ``` * `snapshot` is a snapshot ID, your snapshot's slug, or a public snapshot such as `freestyle/ubuntu-sm`. * The minimums match the snapshot's size, so agents can't request a machine smaller than the snapshot. * `sleep_after_ms` turns on [sleep](/machines/providers/overview#sleep-when-idle). Omit it to keep machines awake. * Optional: `startup_script` in `default_machine_provider_options` runs before the daemon starts, including after a reboot, and `allowed_snapshots` in `provider_config` is an [allowlist](/machines/providers/overview#allowlists). # Modal Source: https://docs.omnara.com/machines/providers/modal Trusted infrastructure for untrusted agents, at the scale they demand [Modal](https://modal.com) is trusted infrastructure for untrusted agents: high-performance sandboxes that give agents the computers they need at the scale they demand. Each machine runs in a Modal Sandbox, a container isolated with gVisor. Connect your Modal workspace with a custom pool. ## Why Modal * **Scale to a million in under a minute.** Modal's infrastructure scales to the parallelism production agent systems demand. * **Fast on your own image.** Bring a public `linux/amd64` image and your setup scripts, with no Modal-specific build. Modal caches each image after its first pull, so later machines start quickly. * **Your choice of region.** Pick a Modal region in the US, Europe, Asia-Pacific, and elsewhere, or let Modal place machines wherever it has capacity. * **One home for your AI workloads.** Teams already running inference or training on Modal keep agent machines in the same workspace, with their logs and resource charts in the Modal dashboard. ## Set up a Modal pool **1. Create a token** On Team and Enterprise plans, create a [service user](https://modal.com/settings/tokens/service-users), copy its token ID and secret, and grant it **Contributor** on the environment the pool will use. On other plans, create an API token under **Settings > API Tokens** or with `modal token new`. Store the token as an org-owned [secret](/organization/secrets) whose value is a JSON object with both parts: ```json theme={null} {"token_id":"ak-...","token_secret":"as-..."} ``` **2. Choose an image** Pick a public `linux/amd64` image that meets the [custom image requirements](/machines/providers/overview#custom-image-requirements). If it lacks `curl`, a startup script can install it. * Use a versioned tag or a digest. Modal caches an image the first time it pulls it, so later changes to a tag such as `latest` aren't picked up. * The image's `ENTRYPOINT`, if it has one, must run the command it's given. Omnara starts the daemon as the sandbox's command. * Machines run on gVisor, Modal's default runtime, so pick an image that doesn't need Docker or systemd. * Each sandbox runs for up to 24 hours, so put setup in the image or startup script rather than on the machine. **3. Create the pool** On the **Machines** page, click **New pool** and choose **Modal**. Enter the credential, image, CPU and memory, and capacity limits. Leave **Region** empty for automatic placement, and leave **App** empty to use `omnara`, which Omnara creates on first use. Environment variable names can use only letters, digits, and underscores and can't start with a digit. Modal rejects other names, so machines that use them fail to start. To use the API instead, call [Create a pool](/machines/pools#create-a-pool) with the fields under [API fields](#api-fields). The dashboard uses the `main` environment; to use another, create it in Modal and set `environment` through the API. ## API fields When you create a pool through the API, send these fields in the [Create a pool](/machines/pools#create-a-pool) request. `provider_config` applies to the whole pool, and `default_machine_provider_options` sets the options for each new machine, which project grants and agent configs can override. For example: ```jsonc theme={null} { "name": "modal-pool", "provider": "modal", "provider_auth_secret_id": "sec_hcyd5n6a2bfgik7mv4qtrwz3je", "provider_config": { "app": "omnara", // optional "environment": "main" // optional }, "default_machine_provider_options": { "image": "buildpack-deps:24.04-curl" }, "default_machine_cpu": 2, "default_machine_memory_mb": 4096, "max_machine_cpu": 4, "max_machine_memory_mb": 8192, "max_total_cpu": 20, "max_total_memory_mb": 40960, "max_total_machines": 10 } ``` * `app` defaults to `omnara`, which Omnara creates on first use. `environment` defaults to `main`, and other environments must already exist. * `image` is a public `linux/amd64` registry image. * `region` is a Modal region, such as `us` or `eu-west`. Omit it for automatic placement. * CPU is in vCPU. Omnara converts it to Modal's physical cores at two vCPU per core. * Optional: `startup_script` in `default_machine_provider_options` runs on each new machine before the daemon starts, and `allowed_images` and `allowed_regions` in `provider_config` are [allowlists](/machines/providers/overview#allowlists). # Sandbox providers Source: https://docs.omnara.com/machines/providers/overview Compare the sandbox providers you can use with Omnara Omnara supports twelve sandbox providers for [machine pools](/machines/pools), and agents get the same tools on all of them. On Omnara's hosted service, every organization starts with a default pool on Blaxel. Omnara manages it for you, so there's nothing to set up: agents can use it right away. To use another provider, or your own Blaxel account, create a [custom pool](/machines/pools#create-a-pool). ## Compare providers | Provider | Machine type | Custom environment | Locations | Sizing | Can sleep when idle | | - | - | - | - | - | - | | [Agent37](/machines/providers/agent37) | Container (gVisor) | Template from a registry image or Dockerfile | US | CPU and memory | No | | [Arker](/machines/providers/arker) | Full VM | Fork of an Arker VM, memory included | US, EU | CPU and memory | Yes | | [Blaxel](/machines/providers/blaxel) | microVM | Dockerfile that includes Blaxel's `sandbox-api` | US, UK, Germany | Memory; CPU scales with it | Yes | | [boxd](/machines/providers/boxd) | microVM | Snapshot of a boxd machine | EU | Set by the snapshot or org default | Yes | | [CreateOS](/machines/providers/createos) | microVM | Template built from a Dockerfile | Chosen by CreateOS | Set by the shape | Yes | | [Daytona](/machines/providers/daytona) | Container | Snapshot from a registry image or Dockerfile | US, EU | Set by the snapshot | No | | [E2B](/machines/providers/e2b) | microVM | Template built from a Dockerfile, or one of E2B's public templates | US, EU, Asia-Pacific | Set by the template | No | | [Freestyle](/machines/providers/freestyle) | Full VM | Snapshot of a Freestyle VM | Chosen by Freestyle | CPU and memory | Yes | | [Modal](/machines/providers/modal) | Container (gVisor) | Public registry image | US, EU, Asia-Pacific, and more | CPU and memory | No | | [Superserve](/machines/providers/superserve) | microVM | Template built from an OCI image | US | Set by the template | No | | [Tenki](/machines/providers/tenki) | Full VM | Tenki template | Chosen by Tenki | CPU and memory | No | | [Unikraft Cloud](/machines/providers/unikraft) | microVM | Dockerfile built with the Unikraft CLI | US, Germany, Singapore | CPU and memory | Yes | ## Choosing a provider * **Agent37** gives each agent an always-on Linux computer that keeps its files, starts in seconds from any registry image or Dockerfile, and bills by the minute. Every port an agent serves gets its own URL, and you can SSH in to work alongside it. * **Arker** runs hyper-elastic, durable virtual machines for agents at extreme scale. Its custom hypervisor spins machines up, down, and resizes them in milliseconds, and every machine is a faithful fork of a source VM, memory and running processes included. * **Blaxel** runs each machine in its own microVM that boots in milliseconds and [resumes](#sleep-when-idle) in about 25 ms with its memory intact. It's also the quickest way to start, since the default pool already runs on it. * **boxd** gives devs and agents full, hardware-isolated Linux computers in under 10 ms, hosted in Europe. Machines are persistent by default, suspend when idle, and resume in under a millisecond. * **CreateOS** gives every agent its own computer: a Firecracker microVM with its own kernel that boots in about 30 ms. Idle machines pause and resume in place, with no cold boot. * **Daytona** creates sandboxes in under 90 ms and runs AI-generated code in isolation from your infrastructure. It fits teams with existing Docker images, and you can open a terminal on any machine to work alongside your agents. * **E2B** gives every agent a hardware-isolated Firecracker microVM that starts from a snapshot in milliseconds, with public templates for Claude Code, Codex, OpenCode, and more. It runs in the US, EU, and Asia-Pacific, or in your own cloud. * **Freestyle** gives agents full Linux VMs, ready in about 65 ms, for tasks that run for hours, days, or weeks. Nested virtualization runs Docker and VMs inside each machine, and idle VMs pause with their state intact. * **Modal** is trusted infrastructure for untrusted agents. It scales to a million sandboxes in under a minute and runs public registry images in the region you choose. * **Superserve** boots each agent into its own Firecracker microVM in under 50 ms from a template you build once, with per-sandbox egress rules and a secrets proxy that keeps real credentials out of the machine. * **Tenki** gives coding agents full Linux computers that start in under a second, with Claude Code, Codex, and common languages already installed, on hardware Tenki owns end to end. * **Unikraft Cloud** is the millisecond compute layer, with VM-grade isolation and scale to zero in under 10 ms. It's built for agents that spend most of their time waiting, and it can run on your own infrastructure. ## Sleep when idle On Arker, Blaxel, boxd, CreateOS, Freestyle, and Unikraft Cloud, a machine can sleep while it's idle and wake the next time an agent uses it. A sleeping machine keeps its memory, files, and running processes. To turn on sleep, set `sleep_after_ms` in the pool's `default_machine_provider_options` to how long a machine must be idle before it sleeps, in milliseconds (at least `30000`). A machine is idle when no command or process started by an agent is running on it. The default Blaxel pool has sleep turned on already, so its machines stop using compute between turns, which keeps costs down. Turn it on for your custom pools to get the same savings. ## Allowlists By default, project grants and agent configs can only use the image, snapshot, shape, template, or location set in the pool's `default_machine_provider_options`. To let them choose others, list every allowed value, including the default, in the matching `provider_config` allowlist, such as `allowed_images`. Use `["*"]` to allow any value. ## Custom image requirements Omnara installs and starts the daemon on each new machine. A custom image or snapshot must provide: * A writable `HOME` or `OMNARA_HOME`. * A POSIX shell and standard Unix utilities, including `base64`, `curl`, and `sha256sum` or `shasum`. * Outbound HTTPS and WebSocket access to your Omnara API. For a self-hosted deployment, `OMNARA_PUBLIC_API_URL` must be reachable from the provider's network. # Superserve Source: https://docs.omnara.com/machines/providers/superserve Firecracker microVMs for AI agents that boot in under 50 ms from a template you build once [Superserve](https://superserve.ai) runs each machine in its own Firecracker microVM with its own kernel, filesystem, and network namespace. Sandboxes boot from a template in under 50 ms, so there's no image pull or package install when an agent needs a machine. Connect your Superserve team with a custom pool. ## Why Superserve * **A VM boundary for every agent.** Each machine is a Firecracker microVM, not a shared container, so AI-generated code runs isolated from your infrastructure and from other machines. * **Ready in under 50 ms.** Templates are snapshots of a built filesystem. Every machine launches from one already prepared, with nothing to install at run time. * **Omnara's tools, baked in.** `superserve/omnara` is Ubuntu 24.04 with the same development tools as Omnara's [default machine environment](/machines/pools#default-machine-environment), at 2 vCPU and 4 GiB. For your own stack, build a [template](https://docs.superserve.ai/templates/create) from an OCI image plus build steps once, and every machine starts from it. * **Network controls and secrets.** Superserve can restrict each sandbox's egress to the hosts you allow and logs every outbound HTTP and HTTPS connection. Its [secrets](https://docs.superserve.ai/secrets/overview) feature attaches real credentials to outbound requests so the machine only ever holds a stand-in token. ## Set up a Superserve pool **1. Create an API key** In the [Superserve console](https://console.superserve.ai), open **Settings → API keys**, and click **Create key**. Store the key as an org-owned [secret](/organization/secrets) in Omnara. The key selects the team and the region. **2. Choose a template** Leave the template empty to use `superserve/omnara`, which gives each machine 2 vCPU, 4 GiB of memory, and an 8 GiB disk with the [default machine environment](/machines/pools#default-machine-environment) preinstalled: Python, Node.js, Go, PostgreSQL, Redis, `gh`, `rg`, PDF tools, and Playwright with headless Chromium. For pools that only run shell commands, `superserve/base` is Ubuntu 24.04 with `curl` and `git` at 1 vCPU and 1 GiB. For your own tools, build a [Superserve template](https://docs.superserve.ai/templates/create) from a linux/amd64 OCI image that meets the [custom image requirements](/machines/providers/overview#custom-image-requirements), and use its name, such as `acme/agent-box`. A template sets each machine's vCPU, memory, and disk; sandboxes can't override them. **3. Create the pool** On the **Machines** page, click **New pool** and choose **Superserve**. Enter the credential, the template (or leave it empty), the CPU and memory maximums, and capacity limits. The maximums must be at least the template's size. Only environment variables whose names use letters, digits, and underscores, and don't start with a digit, reach the startup script. To use the API instead, call [Create a pool](/machines/pools#create-a-pool) with the fields under [API fields](#api-fields). ## API fields When you create a pool through the API, send these fields in the [Create a pool](/machines/pools#create-a-pool) request. `provider_config` applies to the whole pool, and `default_machine_provider_options` sets the options for each new machine, which project grants and agent configs can override. For example: ```jsonc theme={null} { "name": "superserve-pool", "provider": "superserve", "provider_auth_secret_id": "sec_hcyd5n6a2bfgik7mv4qtrwz3je", "default_machine_provider_options": { "template": "acme/agent-box" // optional }, "max_machine_cpu": 2, "max_machine_memory_mb": 4096, "max_total_cpu": 20, "max_total_memory_mb": 40960, "max_total_machines": 10 } ``` * `template` is optional. Omit it to use `superserve/omnara`, or set the name of a [Superserve system template](https://docs.superserve.ai/templates/overview#system-templates) or a template your team owns. The template sets each machine's size, so CPU and memory defaults are optional, and the maximums must be at least the template's size. * Optional: `startup_script` in `default_machine_provider_options` runs on each new machine before the daemon starts, and `allowed_templates` in `provider_config` is an [allowlist](/machines/providers/overview#allowlists). # Tenki Source: https://docs.omnara.com/machines/providers/tenki Full Linux computers for your AI agents, running in under a second [Tenki Sandbox](https://tenki.cloud/products/sandbox) gives your AI agents full Linux computers: hardware-isolated Ubuntu 24.04 VMs, each with its own kernel. Connect your Tenki workspace with a custom pool. ## Why Tenki * **A real Linux computer.** Each machine runs systemd, and agents get passwordless `sudo`, so they can install Docker or any other package they need. * **Start with your tools installed.** Tenki's base image comes with Claude Code, Codex, and opencode, plus Python, Node.js, Bun, `git`, `gh`, and build tools. Or define your environment once in a [Tenki template](https://tenki.cloud/docs/sandbox/templates) and launch from it every time. * **Your custom image, running in under a second.** Each machine's disk mounts straight from Tenki's storage cluster, so nothing is copied before boot. * **Owned end to end.** Tenki runs its own compute, storage, switches, power, and uplink, so it controls the whole path from request to running machine. * **Long-running agents.** Tenki keeps each machine running, with no time limit, until Omnara deletes it. ## Set up a Tenki pool **1. Create an API key** In the [Tenki dashboard](https://app.tenki.cloud), open **Tenki Sandbox**, go to **API Keys**, and click **Create API Key**. The key selects the workspace. Store the key as an org-owned [secret](/organization/secrets) in Omnara. **2. Choose an image** Leave the image empty to use Tenki's Ubuntu base image with a 20 GB disk. For your own tools, build a [Tenki template](https://tenki.cloud/docs/sandbox/templates) with the Tenki CLI and use the image reference it prints, such as `acme/agent-box:v1`. Leave `runtime` out of the template, since Omnara supplies its own start command. Custom images keep the template's disk size unless the pool sets `disk_size_gb`. **3. Create the pool** On the **Machines** page, click **New pool** and choose **Tenki**. Enter the credential, image (or leave it empty), CPU and memory, and capacity limits. Only environment variables whose names use letters, digits, and underscores, and don't start with a digit, reach the startup script. The environment and startup script together must stay under about 128 KB. To use the API instead, call [Create a pool](/machines/pools#create-a-pool) with the fields under [API fields](#api-fields). The dashboard can't set the disk size, so set `disk_size_gb` through the API. ## API fields When you create a pool through the API, send these fields in the [Create a pool](/machines/pools#create-a-pool) request. `provider_config` applies to the whole pool, and `default_machine_provider_options` sets the options for each new machine, which project grants and agent configs can override. For example: ```jsonc theme={null} { "name": "tenki-pool", "provider": "tenki", "provider_auth_secret_id": "sec_hcyd5n6a2bfgik7mv4qtrwz3je", "default_machine_provider_options": { "disk_size_gb": 40 // optional }, "default_machine_cpu": 2, "default_machine_memory_mb": 4096, "max_machine_cpu": 4, "max_machine_memory_mb": 8192, "max_total_cpu": 20, "max_total_memory_mb": 40960, "max_total_machines": 10 } ``` * `image` is optional. Omit it, as here, to use Tenki's base image, or set a Tenki image reference such as `acme/agent-box:v1`. * `disk_size_gb` ranges from 5 to 100. It defaults to 20 for the base image and to the template's size for custom images. * Memory must be a multiple of 2 MB. * Optional: `startup_script` in `default_machine_provider_options` runs on each new machine before the daemon starts, and `allowed_images` in `provider_config` is an [allowlist](/machines/providers/overview#allowlists). # Unikraft Cloud Source: https://docs.omnara.com/machines/providers/unikraft The millisecond compute layer, with VM-grade isolation and scale to zero [Unikraft Cloud](https://unikraft.com) is the millisecond compute layer for sandboxes and AI agents, with VM-grade isolation and scale to zero. Connect your Unikraft Cloud account with a custom pool. ## Why Unikraft Cloud * **Scale to zero in milliseconds.** Agents often wait on a model, a tool, or a person. With [sleep](/machines/providers/overview#sleep-when-idle) on, Unikraft Cloud snapshots an idle machine, scales it to zero in under 10 ms, and resumes it in milliseconds with its memory, files, and running processes intact. * **The security of VMs, the tools you love.** Each machine is its own hardware-isolated microVM, and you define it with a Dockerfile, just like a container. * **Built for scale.** Unikraft Cloud packs up to 100,000 instances onto a single server and handles millions of microVMs, so it keeps up with fleets of agents. * **Your cloud, your way.** Unikraft's enterprise plan deploys Unikraft Cloud on-premises, in your own cloud account, or on dedicated hosts. Point a pool at your deployment with `api_base_url`. ## Set up a Unikraft pool **1. Create an API token** Sign up for [Unikraft Cloud](https://console.unikraft.cloud) and create an API token in the console. The token has full access to your account. Store it as an org-owned [secret](/organization/secrets) in Omnara. **2. Build an image** Start from the Omnara repository's [example image](https://github.com/omnara-ai/omnara/tree/main/examples/unikraft-machine), which packages the daemon. Building it needs Go, Docker, and the [Unikraft CLI](https://unikraft.com/docs/cli), signed in with `unikraft login`. Add your agents' tools to the example's `Dockerfile` rather than a startup script, since startup scripts have limited space on Unikraft. Then build and push the image from the repository root with your image name and an `omnarad` release version: ```bash theme={null} ./examples/unikraft-machine/build.sh acme/omnara-machine:v1 0.1.0 ``` **3. Create the pool** On the **Machines** page, click **New pool** and choose **Unikraft**. Enter the credential, image, metro, CPU and memory, and capacity limits. Files that agents write are kept in memory, so size memory to fit their repositories and dependencies. To use the API instead, call [Create a pool](/machines/pools#create-a-pool) with the fields under [API fields](#api-fields). The dashboard can't turn on sleep yet, so set `sleep_after_ms` through the API. ## API fields When you create a pool through the API, send these fields in the [Create a pool](/machines/pools#create-a-pool) request. `provider_config` applies to the whole pool, and `default_machine_provider_options` sets the options for each new machine, which project grants and agent configs can override. For example: ```jsonc theme={null} { "name": "unikraft-pool", "provider": "unikraft", "provider_auth_secret_id": "sec_hcyd5n6a2bfgik7mv4qtrwz3je", "default_machine_provider_options": { "image": "acme/omnara-machine:v1", "metro": "fra", "sleep_after_ms": 300000 // optional }, "default_machine_cpu": 1, "default_machine_memory_mb": 2048, "max_machine_cpu": 2, "max_machine_memory_mb": 4096, "max_total_cpu": 4, "max_total_memory_mb": 8192, "max_total_machines": 4 } ``` * `image` is an image in Unikraft's registry. * `metro` is `fra`, `sfo`, `dal`, `was`, or `sin`. * `sleep_after_ms` turns on [sleep](/machines/providers/overview#sleep-when-idle). Omit it to keep machines awake. * Optional: a short `startup_script` in `default_machine_provider_options` runs on each new machine before the daemon starts. In `provider_config`, `api_base_url` points the pool at a custom Unikraft Cloud API (an HTTPS URL without a path, used for every metro), and `allowed_images` and `allowed_metros` are [allowlists](/machines/providers/overview#allowlists). # Members & access Source: https://docs.omnara.com/organization/members Manage organizations, projects, members, roles, and invitations An **organization** is the top-level account. **Projects** contain agents and configs, and control which shared resources they can use through grants. Each person has one organization role and, optionally, a role in individual projects. ## Roles **Org roles** — held by every member: | Role | Can | | - | - | | `owner` | Everything, including deleting the org and managing owners | | `admin` | Manage the org: members, projects, secrets, machines, models — everything but deleting the org or managing owners | | `member` | See the org; project access comes only from explicit project roles | **Project roles** — granted per member, per project: | Role | Read | Operate agents | Manage project | Manage access | | - | - | - | - | - | | `admin` | ✓ | ✓ | ✓ | ✓ | | `developer` | ✓ | ✓ | ✓ | — | | `operator` | ✓ | ✓ | — | — | | `viewer` | ✓ | — | — | — | **Operate agents** means launching agents, sending [inputs](/events/sending-input), resolving [interactions](/events/interactions), and stopping work. **Manage project** means creating and changing project resources, such as configs, profiles, and project-owned secrets. **Manage access** means assigning project roles and managing grants for shared resources such as models, machines, and pools. Organization owners and admins automatically have full access to every project, so they do not need separate project roles. ## Organizations Your first org is created during onboarding. Creation atomically sets up the org, a default project, and your `owner` membership. > [tab] API > > The examples assume the client, `$ORG`/`orgID`, and `$PROJ`/`projectID` setup from the [quickstart](/quickstart). > > ```bash CLI theme={null} > npx omnara orgs create --name "Acme Research" --json > ``` > > ```bash REST theme={null} > curl "$OMNARA_API/orgs" \ > -H "Authorization: Bearer $OMNARA_TOKEN" \ > -H "Content-Type: application/json" \ > -d '{"name": "Acme Research"}' > ``` > > ```typescript SDK theme={null} > const created = await sdk.createOrganization({ > client, > body: { name: 'Acme Research' }, > }) > ``` > > ```json theme={null} > { > "org": { "id": "org_k7mv4qtrwz3jehcyd5n6a2bfgi", "name": "Acme Research" }, > "project": { "id": "proj_7mv4qtrwz3jehcyd5n6a2bfgik", "name": "Default" }, > "membership": { "role": "owner" } > } > ``` > > `omnara whoami` and [`GET /me`](/api/authentication) (`getCurrentUser` in the SDK) list the organizations you belong to, and `omnara config select` switches the CLI's default organization and project. Deleting an organization (`omnara orgs delete {org-id}`, `DELETE /orgs/{orgID}`, `deleteOrganization`) is owner-only and removes everything in it. > [tab] Dashboard > > Click the organization name at the top of the sidebar and choose **New organization**. Use the same menu to switch organizations. ## Projects > [tab] API > > ```bash CLI theme={null} > npx omnara projects create --name release-automation --json > ``` > > ```bash REST theme={null} > curl "$OMNARA_API/orgs/$ORG/projects" \ > -H "Authorization: Bearer $OMNARA_TOKEN" \ > -H "Content-Type: application/json" \ > -d '{"name": "release-automation"}' > ``` > > ```typescript SDK theme={null} > const project = await sdk.createProject({ > client, > path: { orgID }, > body: { name: 'release-automation' }, > }) > ``` > > ```json theme={null} > { "id": "proj_7mv4qtrwz3jehcyd5n6a2bfgik", "name": "release-automation", "...": "..." } > ``` > [tab] Dashboard > > Open the project switcher in the page header, click **New project**, and give it a name. Project lists include only projects you can access. Each project includes `access.can_read`, `can_operate`, `can_manage`, and `can_manage_access`, so clients do not need to recreate the role matrix. ## Invite people Invitations are org-scoped and email-addressed. Invite as `owner`, `admin`, or `member`; only owners can invite owners. > [tab] API > > ```bash CLI theme={null} > npx omnara members invites create --email priya@acme.dev --role member --json > ``` > > ```bash REST theme={null} > curl "$OMNARA_API/orgs/$ORG/invitations" \ > -H "Authorization: Bearer $OMNARA_TOKEN" \ > -H "Content-Type: application/json" \ > -d '{"email": "priya@acme.dev", "role": "member"}' > ``` > > ```typescript SDK theme={null} > const invitation = await sdk.createOrgInvitation({ > client, > path: { orgID }, > body: { email: 'priya@acme.dev', role: 'member' }, > }) > ``` > > ```json theme={null} > { > "id": "oinv_qtrwz3jehcyd5n6a2bfgik7mv4", > "email": "priya@acme.dev", > "org_role": "member" > } > ``` > > Organization admins can list pending invitations with `omnara members invites list` or `GET /orgs/$ORG/invitations` (`listOrgInvitations`), and revoke one with `omnara members invites delete {invitation-id}` or `DELETE /orgs/$ORG/invitations/{invitationID}` (`deleteOrgInvitation`). > > The invitee can list and accept invitations through their own account: > > ```bash CLI theme={null} > npx omnara members invites pending > npx omnara members invites accept oinv_qtrwz3jehcyd5n6a2bfgik7mv4 > ``` > > ```bash REST theme={null} > curl "$OMNARA_API/invitations" -H "Authorization: Bearer $PRIYA_TOKEN" > curl -X POST "$OMNARA_API/invitations/oinv_qtrwz3jehcyd5n6a2bfgik7mv4/accept" -H "Authorization: Bearer $PRIYA_TOKEN" > ``` > > ```typescript SDK theme={null} > await sdk.listPendingInvitations({ client }) > await sdk.acceptInvitation({ > client, > path: { invitationID: 'oinv_qtrwz3jehcyd5n6a2bfgik7mv4' }, > }) > ``` > > Accepting creates the organization membership. The invitation is then removed from the pending list. > [tab] Dashboard > > With **All projects** selected in the project switcher, open **Members** and click **Invite user**: email plus role. Pending invitees appear in the list until accepted; **Revoke invitation** withdraws one. The invitee accepts from their own account when they sign in. ## Manage members > [tab] API > > ```bash CLI theme={null} > npx omnara members list > ``` > > ```bash REST theme={null} > curl "$OMNARA_API/orgs/$ORG/members" -H "Authorization: Bearer $OMNARA_TOKEN" > ``` > > ```typescript SDK theme={null} > const members = await sdk.listOrgMembers({ client, path: { orgID } }) > ``` > > The response includes each member's user ID, email, display name, and organization role. Use the user ID to change their role or remove them: > > ```bash CLI theme={null} > npx omnara members update usr_trwz3jehcyd5n6a2bfgik7mv4q --role admin > npx omnara members remove usr_trwz3jehcyd5n6a2bfgik7mv4q > ``` > > ```bash REST theme={null} > curl -X PATCH "$OMNARA_API/orgs/$ORG/members/usr_trwz3jehcyd5n6a2bfgik7mv4q" \ > -H "Authorization: Bearer $OMNARA_TOKEN" \ > -H "Content-Type: application/json" \ > -d '{"role": "admin"}' > > curl -X DELETE "$OMNARA_API/orgs/$ORG/members/usr_trwz3jehcyd5n6a2bfgik7mv4q" \ > -H "Authorization: Bearer $OMNARA_TOKEN" > ``` > > ```typescript SDK theme={null} > await sdk.updateOrgMember({ > client, > path: { orgID, userID: 'usr_trwz3jehcyd5n6a2bfgik7mv4q' }, > body: { role: 'admin' }, > }) > > await sdk.removeOrgMember({ > client, > path: { orgID, userID: 'usr_trwz3jehcyd5n6a2bfgik7mv4q' }, > }) > ``` > [tab] Dashboard > > The **Members** page (with **All projects** selected in the project switcher) lists everyone with their org role. Expand a member to change their **Org role** or **Remove from organization**. > [!WARNING] > > Only owners can grant the owner role or change or remove an owner, and the last owner cannot be demoted or removed. Demoting an owner cancels the owner invitations they sent. Removing another member permanently deletes the secrets and skills they own in this organization and cancels invitations they sent. Removal fails if one of their secrets is still used by another resource. ## Give members project access Organization members need a project role to access a project. Owners and admins already have access to every project. Use `operator` for teammates who should run agents and handle approvals without changing configs, profiles, or project-owned secrets. > [tab] API > > There is no CLI command for project roles yet. `PUT` creates or changes the member's role: > > ```bash REST theme={null} > curl -X PUT "$OMNARA_API/orgs/$ORG/members/usr_trwz3jehcyd5n6a2bfgik7mv4q/projects/$PROJ" \ > -H "Authorization: Bearer $OMNARA_TOKEN" \ > -H "Content-Type: application/json" \ > -d '{"role": "operator"}' > ``` > > ```typescript SDK theme={null} > await sdk.setMemberProjectAccess({ > client, > path: { orgID, userID: 'usr_trwz3jehcyd5n6a2bfgik7mv4q', projectID }, > body: { role: 'operator' }, > }) > ``` > > `GET …/projects` (`listMemberProjectAccess`) lists the member's explicit project roles. `DELETE` on the same project path (`removeMemberProjectAccess`) removes one. > [tab] Dashboard > > 1. With **All projects** selected in the project switcher, open **Members** and expand the member. > 2. Under **Project access**, choose a project and role. > 3. Click **Add**. > > Use the role menu to change access or **Remove** to revoke it. > [!NOTE] > > Full schema and playground: [Organizations and Projects](/api-reference/endpoints/organizations-and-projects/create-organization) · [Members](/api-reference/endpoints/organizations-and-projects/list-organization-members) · [Invitations](/api-reference/endpoints/organizations-and-projects/create-org-invitation). # Model providers Source: https://docs.omnara.com/organization/model-providers Connect providers, configure models, and grant them to projects Omnara works with the model providers you choose. Before an agent can use a model, your organization connects the provider, configures the model, and grants it to the project: ```mermaid theme={null} graph LR S["Secret
sec_… (API key)"] --> C["Provider config
mpc_…"] C --> M["Configured model
mdl_…"] M --> G["Project grant
pmog_…"] G --> Y["Agent config YAML
by name"] ``` The organization owns the credentials and model settings. Projects receive access through grants, and agent configs reference the provider and model by name. The dashboard guides you through this chain. The [CLI](/quickstart) and API expose the same resources for automation; CLI examples assume you have run `omnara login` and `omnara config select`. ## Connect a provider > [tab] API > > The examples assume the client, `$ORG`/`orgID`, and `$PROJ`/`projectID` setup from the [quickstart](/quickstart). > > Store the API key as a secret, then create a provider that references it. The preset supplies the endpoint, API format, and authentication settings: > > ```bash CLI theme={null} > npx omnara secrets create \ > --owner-kind org \ > --name openai-api-key \ > --material-kind generic \ > --material-value "sk-..." > ``` > > ```bash REST theme={null} > curl "$OMNARA_API/orgs/$ORG/secrets" \ > -H "Authorization: Bearer $OMNARA_TOKEN" \ > -H "Content-Type: application/json" \ > -d '{ > "owner": { "kind": "org" }, > "name": "openai-api-key", > "material": { "kind": "generic", "value": "sk-..." } > }' > ``` > > ```typescript SDK theme={null} > const secret = await sdk.createSecret({ > client, > path: { orgID }, > body: { > owner: { kind: 'org' }, > name: 'openai-api-key', > material: { kind: 'generic', value: 'sk-...' }, > }, > }) > ``` > > Keep the returned `id`, then: > > ```bash CLI theme={null} > npx omnara model-providers create \ > --name openai-prod \ > --preset openai \ > --credential-secret-id sec_hcyd5n6a2bfgik7mv4qtrwz3je \ > --json > ``` > > ```bash REST theme={null} > curl "$OMNARA_API/orgs/$ORG/model-provider-configs" \ > -H "Authorization: Bearer $OMNARA_TOKEN" \ > -H "Content-Type: application/json" \ > -d '{ > "name": "openai-prod", > "preset": "openai", > "credential_secret_id": "sec_hcyd5n6a2bfgik7mv4qtrwz3je" > }' > ``` > > ```typescript SDK theme={null} > const provider = await sdk.createModelProviderConfig({ > client, > path: { orgID }, > body: { > name: 'openai-prod', > preset: 'openai', > credential_secret_id: secret.data.id, > }, > }) > ``` > > ```json theme={null} > { > "config": { > "id": "mpc_bfgik7mv4qtrwz3jehcyd5n6a2", > "name": "openai-prod", > "api_format": "openai-responses", > "base_url": "https://api.openai.com/v1", > "endpoint_path": "/responses", > "credential_secret_id": "sec_hcyd5n6a2bfgik7mv4qtrwz3je", > "...": "..." > }, > "model_catalog": { > "status": "ok", > "models": [ > { "slug": "gpt-6-sol", "display_name": "GPT-6 Sol" } > ] > } > } > ``` > > Presets are available for `openai`, `anthropic`, and `openrouter`. For a proxy, local vLLM server, or another compatible endpoint, set the API format, base URL, endpoint path, and authentication method yourself. > > Omnara fetches the provider's model catalog when it creates the provider, and on demand with `omnara model-providers catalog {model-provider-config-id}`, `GET …/model-provider-configs/{id}/model-catalog`, or `sdk.getModelCatalog`. The catalog probe can fail without failing the request, which allows private or air-gapped endpoints. Check `model_catalog.status`; you can configure models manually if needed. > > A provider cannot be deleted while it still has configured models. > [tab] Dashboard > > **1. Open the provider dialog** > > With **All projects** selected in the project switcher, open **Models** and click **New provider**. Pick a provider and name the connection — this name (e.g. `openai-prod`) is what agent configs reference as `model.provider_config`. > > **2. Create the credential inline** > > In the API key field, click **New secret**, name it, paste the provider's API key, and click **Create secret**. It's stored as an organization [secret](/organization/secrets) (`sec_…`); the provider only ever holds the reference. > > **3. Add provider** > > Click **Add provider**. Omnara creates the provider and tries to discover its models. If discovery fails, the provider is still created and you can add models manually. Provider timeouts apply to each attempt, including reasoning and response streaming: * `request_timeout_ms` sets the total deadline, defaulting to 3600000 (60 minutes). * `idle_timeout_ms` limits the wait for response headers or more response data, defaulting to 300000 (5 minutes). Heartbeats count as activity. Increase it for endpoints that can remain silent for longer periods. Edit these settings in the provider dialog or through the API. Existing total deadlines are retained on upgrade. Use `headers` to send extra HTTP headers with every request to the provider, and `secret_headers` to take a header's value from a [secret](/organization/secrets); put keys and tokens in `secret_headers`. Set both in the provider dialog or through the API. If a referenced secret is deleted, requests omit that header. ## Configure models A configured model (`mdl_…`) gives a provider model a name and runtime settings. Agent configs use `name`; Omnara sends `provider_model_slug` to the provider. Keeping them separate lets you manage provider details centrally. > [tab] API > > ```bash CLI theme={null} > npx omnara models create mpc_bfgik7mv4qtrwz3jehcyd5n6a2 \ > --name "gpt-6-sol" \ > --provider-model-slug gpt-6-sol \ > --context-window-tokens 1050000 \ > --max-output-tokens 128000 \ > --supports-reasoning \ > --default-reasoning-effort medium \ > --default-cache-retention short \ > --json > ``` > > ```bash REST theme={null} > curl "$OMNARA_API/orgs/$ORG/model-provider-configs/mpc_bfgik7mv4qtrwz3jehcyd5n6a2/models" \ > -H "Authorization: Bearer $OMNARA_TOKEN" \ > -H "Content-Type: application/json" \ > -d '{ > "name": "gpt-6-sol", > "provider_model_slug": "gpt-6-sol", > "context_window_tokens": 1050000, > "max_output_tokens": 128000, > "supports_reasoning": true, > "default_reasoning_effort": "medium", > "default_cache_retention": "short" > }' > ``` > > ```typescript SDK theme={null} > const model = await sdk.createConfiguredModel({ > client, > path: { orgID, modelProviderConfigID: 'mpc_bfgik7mv4qtrwz3jehcyd5n6a2' }, > body: { > name: 'gpt-6-sol', > provider_model_slug: 'gpt-6-sol', > context_window_tokens: 1050000, > max_output_tokens: 128000, > supports_reasoning: true, > default_reasoning_effort: 'medium', > default_cache_retention: 'short', > }, > }) > ``` > > ```json theme={null} > { > "id": "mdl_fgik7mv4qtrwz3jehcyd5n6a2b", > "name": "gpt-6-sol", > "provider_model_slug": "gpt-6-sol", > "context_window_tokens": 1050000, > "supports_reasoning": true, > "default_reasoning_effort": "medium", > "current_revision_id": "mrev_gik7mv4qtrwz3jehcyd5n6a2bf", > "...": "..." > } > ``` > [tab] Dashboard > > In **Add models**, select the detected models you want, or click **Skip for now**. The detected slug is used as the configured model name. Agent YAML must use the configured model name exactly. > > To add models later, select **All projects** in the project switcher, open **Models**, expand the provider's models, and click **Add model**; the provider's own page has the same button above its model list. In **Add models**, the provider is preselected; search the provider's models and check the ones you want, or type a slug that isn't listed and click its **Add … as a custom model** row. Click **Edit** on a selected row to set its **Name**, **Context**, and **Max output**; a row that still needs input shows **Needs details**. Optionally pick projects under **Share with**, then click the add button, which shows the count (for example, **Add 2 models**). > > A provider can have several configured models for the same slug — for example, two names with different limits. Models the provider already has are marked **Added**; check one again to add another configuration under a different name. > > Detected models are added with their reasoning support when it is known, including the effort levels an agent can pick as its [reasoning effort](/agents/configuration#model). A model added as a custom slug starts with reasoning off. > > To set the optional **Default output** allowance or change other settings later, open the model's row actions under its provider on the **Models** page and click **Edit**. Token settings have separate roles: * `context_window_tokens` is the budget for input and output tokens. Compaction runs when estimated input exceeds the remaining budget after output and safety reserves. The turn fails if compaction cannot make it fit. * `max_output_tokens` is the model's output capacity. It is optional; selecting a discovered model can prefill it. * `default_max_output_tokens` is an optional output allowance for each request. When omitted, Omnara uses the known output capacity. Omnara fits the request allowance to the remaining context. If both output settings are omitted, Messages uses 64,000 tokens; Chat Completions and Responses use the provider default. Set an explicit capacity or allowance for Messages endpoints that support less. Agents automatically continue after an output-limit cutoff. Changing a model's runtime settings creates a new **revision** (`mrev_…`). Agents resolve the current revision before each model call, so the new settings apply to later calls from existing agents too. ## Grant models to projects A project needs a model grant (`pmog_…`) before its configs can use the model. > [tab] API > > ```bash CLI theme={null} > npx omnara grant models add --configured-model-id mdl_fgik7mv4qtrwz3jehcyd5n6a2b --json > ``` > > ```bash REST theme={null} > curl "$OMNARA_API/orgs/$ORG/projects/$PROJ/model-grants" \ > -H "Authorization: Bearer $OMNARA_TOKEN" \ > -H "Content-Type: application/json" \ > -d '{"configured_model_id": "mdl_fgik7mv4qtrwz3jehcyd5n6a2b"}' > ``` > > ```typescript SDK theme={null} > await sdk.createProjectModelGrant({ > client, > path: { orgID, projectID }, > body: { configured_model_id: 'mdl_fgik7mv4qtrwz3jehcyd5n6a2b' }, > }) > ``` > > ```json theme={null} > { > "grant": { > "id": "pmog_ik7mv4qtrwz3jehcyd5n6a2bfg", > "configured_model_id": "mdl_fgik7mv4qtrwz3jehcyd5n6a2b", > "...": "..." > } > } > ``` > > A grant can apply stricter settings for one project, such as lower token limits, disabled reasoning, or fewer input and output types — set them when creating the grant (for example, with the CLI's `--max-output-tokens` or `--no-supports-reasoning` flags). Settings you omit inherit from the configured model. `omnara grant models list` shows a project's grants and `omnara grant models delete {model-grant-id}` revokes one. > > Revoking a grant prevents the project from creating new configs with that model. It does not change agents that already use it. > [tab] Dashboard > > Two ways to share the same model: > > * From the model: on the organization **Models** page, use the model's row action **Share with project** (or pick projects under **Share with** while creating the model). > * From the project: in the project's sidebar, open **Models** and click **Share models**. This page also lists the project's shared models. Click **Stop sharing** to remove one. ## Use it from a config With the chain complete, this config can now be created in the project: ```yaml theme={null} model: provider_config: openai-prod name: gpt-6-sol ``` When you create the config, Omnara checks the provider, model, and project grant. If something is missing, the validation error tells you what to fix. > [!NOTE] > > Full schema and playground: [Create model provider config](/api-reference/endpoints/models/create-model-provider-config) · [Create configured model](/api-reference/endpoints/models/create-configured-model) · [Project model grants](/api-reference/endpoints/models/create-project-model-grant). # Secrets Source: https://docs.omnara.com/organization/secrets Store credentials securely, rotate their values, and share them with projects A **secret** stores a credential such as a model API key, machine environment value, MCP token, or integration credential. Other resources reference the secret by its `sec_…` ID instead of storing the value themselves. Secret values are encrypted and never returned by read endpoints. Rotating a secret updates the value used by its existing references, so nothing needs to be redeployed. ## Kinds and owners You can create these kinds: | `kind` | Holds | Created by | | - | - | - | | `generic` | One value, such as an API key, token, or password | You | | `oauth_token_set` | Access/refresh tokens with refresh metadata | The [MCP OAuth flow](/tools/mcp#connect-with-oauth), or you | | `aws_credentials` | AWS access credentials, with an optional session token or role to assume | You | | `github_app_credentials` | GitHub App ID, RSA private key and webhook secret | You; see [GitHub setup](/integrations/github#connect-your-github-app) | Every secret has exactly one owner. A project can use a secret only if the project owns it or holds an explicit [grant](#grant-a-secret-to-a-project) — org and user ownership don't make a secret visible to projects on their own: | Owner | Typical use | | - | - | | `{ "kind": "org" }` | Org-level resources — [model provider](/organization/model-providers) credentials, [machine pool](/machines/pools) API keys. Grant it to projects that need it directly. | | `{ "kind": "project", "project_id": … }` | That project's agent configs and machines | | `{ "kind": "user" }` | Personal credentials; grant to the projects that need them | The convention that keeps orgs tidy: own each secret at the narrowest scope that needs it. A model key the whole org shares is org-owned; one project's GitHub deploy token is project-owned. ## Create a secret > [tab] API > > The examples assume the client, `$ORG`/`orgID`, and `$PROJ`/`projectID` setup from the [quickstart](/quickstart). > > ```bash CLI theme={null} > npx omnara secrets create \ > --owner-kind project \ > --owner-project-id proj_7mv4qtrwz3jehcyd5n6a2bfgik \ > --name github-deploy-token \ > --metadata '{"rotates": "quarterly"}' \ > --material-kind generic \ > --material-value "ghp_..." \ > --json > ``` > > ```bash REST theme={null} > curl "$OMNARA_API/orgs/$ORG/secrets" \ > -H "Authorization: Bearer $OMNARA_TOKEN" \ > -H "Content-Type: application/json" \ > -d '{ > "owner": { "kind": "project", "project_id": "'"$PROJ"'" }, > "name": "github-deploy-token", > "metadata": { "rotates": "quarterly" }, > "material": { "kind": "generic", "value": "ghp_..." } > }' > ``` > > ```typescript SDK theme={null} > const secret = await sdk.createSecret({ > client, > path: { orgID }, > body: { > owner: { kind: 'project', project_id: projectID }, > name: 'github-deploy-token', > metadata: { rotates: 'quarterly' }, > material: { kind: 'generic', value: 'ghp_...' }, > }, > }) > ``` > > ```json theme={null} > { > "id": "sec_7jehcyd5n6a2bfgik7mv4qtrwz", > "owner": { "kind": "project", "project_id": "proj_7mv4qtrwz3jehcyd5n6a2bfgik" }, > "name": "github-deploy-token", > "kind": "generic", > "current_version_number": 1, > "...": "..." > } > ``` > > For MCP OAuth, use the built-in [OAuth flow](/tools/mcp#connect-with-oauth) — `npx omnara secrets mcp-oauth` runs it end to end from the terminal. If you need to import OAuth tokens yourself, see the [secret schema](/api-reference/endpoints/secrets/create-secret). > [tab] Dashboard > > With **All projects** selected in the project switcher, open **Secrets** and pick the **User** or **Organization** tab; for a project secret, open **Secrets** inside the project. Click **New secret**, name it, and choose **Generic**, **OAuth token set**, **MCP OAuth secret**, or **AWS credentials**. For MCP OAuth, click **Authorize and Create Secret** to run the [OAuth flow](/tools/mcp#connect-with-oauth). You can also pick projects under **Share with** in the same dialog. On both surfaces, reads return the secret's metadata, never its value. If you lose the value, rotate the secret. ## Rotate a secret Rotation creates a new version while keeping the same secret ID. The CLI shows the current version but cannot rotate it. ```bash REST theme={null} curl "$OMNARA_API/orgs/$ORG/secrets/sec_7jehcyd5n6a2bfgik7mv4qtrwz/versions" \ -H "Authorization: Bearer $OMNARA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"material": {"kind": "generic", "value": "ghp_new..."}}' ``` ```typescript SDK theme={null} const rotated = await sdk.createSecretVersion({ client, path: { orgID, secretID: 'sec_7jehcyd5n6a2bfgik7mv4qtrwz' }, body: { material: { kind: 'generic', value: 'ghp_new...' } }, }) ``` The response has `current_version_number: 2`. Existing references use the new current value without changing their `sec_…` ID. Processes already running keep the environment they started with. ## Manage secrets Use the secrets pages or API to search for secrets. **Edit** can change a secret's name and value; the API also supports metadata updates. You cannot delete a secret while a model provider, machine pool, or integration uses it as its credential. Replace the secret on that resource — or delete the resource — first. ## Grant a secret to a project A grant lets a project use a secret it does not own. > [tab] API > > ```bash CLI theme={null} > npx omnara grant secrets add sec_7jehcyd5n6a2bfgik7mv4qtrwz \ > --target-project-id proj_4qtrwz3jehcyd5n6a2bfgik7mv \ > --json > ``` > > ```bash REST theme={null} > curl "$OMNARA_API/orgs/$ORG/secrets/sec_7jehcyd5n6a2bfgik7mv4qtrwz/grants" \ > -H "Authorization: Bearer $OMNARA_TOKEN" \ > -H "Content-Type: application/json" \ > -d '{"target_project_id": "proj_4qtrwz3jehcyd5n6a2bfgik7mv"}' > ``` > > ```typescript SDK theme={null} > const grant = await sdk.createSecretGrant({ > client, > path: { orgID, secretID: 'sec_7jehcyd5n6a2bfgik7mv4qtrwz' }, > body: { target_project_id: 'proj_4qtrwz3jehcyd5n6a2bfgik7mv' }, > }) > ``` > > ```json theme={null} > { > "id": "sgr_yd5n6a2bfgik7mv4qtrwz3jehc", > "secret_id": "sec_7jehcyd5n6a2bfgik7mv4qtrwz", > "target_project_id": "proj_4qtrwz3jehcyd5n6a2bfgik7mv", > "...": "..." > } > ``` > > To review a secret's project grants, use `npx omnara grant secrets list {secret-id}`, `GET /orgs/$ORG/secrets/{secret-id}/grants`, or `sdk.listSecretGrants`. To remove one, use `npx omnara grant secrets delete {secret-id} {grant-id}`, `DELETE` on the grant, or `sdk.deleteSecretGrant`. > > To list every secret a project can use (no CLI command for this yet): > > ```bash REST theme={null} > curl "$OMNARA_API/orgs/$ORG/projects/$PROJ/secrets" -H "Authorization: Bearer $OMNARA_TOKEN" > ``` > > ```typescript SDK theme={null} > const available = await sdk.listProjectAvailableSecrets({ > client, > path: { orgID, projectID }, > }) > ``` > > This includes secrets owned by the project and secrets explicitly granted to it. `availability.source` is `direct` for project-owned secrets and `grant` for granted secrets. > [tab] Dashboard > > On the secret's row, click **Share with project** and pick the target project (or pick projects under **Share with** while creating the secret). To view or revoke a project's shared secrets, open the project's **Secrets** page and select the **Shared** tab. Click **Stop sharing secret** to remove access without deleting the secret. ## Where secret IDs are used These fields reference a secret instead of storing its value directly: | Surface | Field | | - | - | | [Model provider config](/organization/model-providers) | `credential_secret_id`, `secret_headers` | | [Machine](/machines/connect) environment | `secret_env` | | [Pool](/machines/pools) environment | `default_machine_secret_env`; project-specific pool grants can add or override values with `default_machine_secret_env_overlay` | | Agent config [machine sources](/agents/configuration#machines) | `secret_env_overlay` | | Agent config [MCP auth](/tools/mcp) | `auth.secret_id` | | [Machine pool](/machines/pools) provider | `provider_auth_secret_id` | > [!NOTE] > > Full schema and playground: [Create secret](/api-reference/endpoints/secrets/create-secret) · [Create secret version](/api-reference/endpoints/secrets/create-secret-version) · [Secret grants](/api-reference/endpoints/secrets/create-secret-grant) · [Project secrets](/api-reference/endpoints/secrets/list-secrets-available-to-project). # Quickstart Source: https://docs.omnara.com/quickstart Define an agent config, grant the resources, and launch an agent with the dashboard, the CLI, the REST API, or the TypeScript SDK Launch your first agent with Omnara via the **dashboard**, or programmatically with the **CLI**, the **REST API**, or the **TypeScript SDK**. ## Before you start Sign in at [app.omnara.com](https://app.omnara.com). When you sign in the first time, Omnara creates your first organization and project, plus two default resources this guide uses: * **`omnara-openrouter`** — a model provider with frontier models such as `anthropic/claude-fable-5.1` and `openai/gpt-6.1-sol`. * **`default-pool`** — a pool of Omnara-managed sandboxes with [common development tools preinstalled](/machines/pools#default-machine-environment). ## Setup with a Skill Use [the skill located here](https://github.com/omnara-ai/omnara/tree/main/skills/setup-omnara-agent/SKILL.md) to setup Omnara for your project. Alternatively, send this prompt to your agent: ``` Help me setup Omnara by fetching the Skill file here: https://raw.githubusercontent.com/omnara-ai/omnara/main/skills/setup-omnara-agent/SKILL.md ``` ## Create and launch the agent > [tab] API > > You can interact with Omnara via the **CLI**, the **REST API**, or the **TypeScript SDK**. > > **1. Set up** > > The CLI signs in through your browser; if you have not logged in yet, the first command that needs your account starts the login for you. The REST API and the SDK authenticate with an API token. In the web browser, open your account menu at the bottom of the sidebar, choose **Personal tokens**, click **New token**, and save the value. > > ```bash CLI theme={null} > npx omnara login > npx omnara config select > npx omnara projects list > ``` > > ```bash REST theme={null} > export OMNARA_API="https://api.omnara.com/v1" # or your self-hosted origin + /api/v1 > export OMNARA_TOKEN="omnara_pat_v1_..." > > export ORG="$(curl -fsS "$OMNARA_API/me" \ > -H "Authorization: Bearer $OMNARA_TOKEN" | jq -r '.orgs[0].id')" > export PROJ="$(curl -fsS "$OMNARA_API/orgs/$ORG/projects?limit=1" \ > -H "Authorization: Bearer $OMNARA_TOKEN" | jq -r '.data[0].id')" > ``` > > ```typescript SDK theme={null} > // npm install @omnara/sdk > import { createOmnaraClient, bearerToken, sdk } from '@omnara/sdk' > > const token = process.env.OMNARA_TOKEN > if (!token) throw new Error('Set OMNARA_TOKEN') > > const client = createOmnaraClient({ > baseUrl: 'https://api.omnara.com/v1', > auth: bearerToken(token), > }) > > const me = await sdk.getCurrentUser({ client }) > const orgID = me.data.orgs[0].id > > const projects = await sdk.listVisibleProjects({ > client, > path: { orgID }, > query: { limit: 1 }, > }) > const projectID = projects.data.data[0].id > ``` > > **2. Write the config** > > Save this config as `agent.yaml`. The agent runs commands on the cloud machine that Omnara creates from `default-pool` at launch: > > ```yaml theme={null} > version: v1 > instruction: | > You are an engineering assistant. Use your machine to run > commands. Use web search to find current information. Ask a > question when a task is not clear. > model: > provider_config: omnara-openrouter > name: anthropic/claude-fable-5.1 > tools: > web_search: {} > web_fetch: {} > ask_question: {} > machine_sources: > - machine_pool_name: default-pool > ``` > > **3. Upload the config and launch the agent** > > ```bash CLI theme={null} > npx omnara agents launch --file agent.yaml \ > --name "Machine assistant" \ > --message "Use your machine to report the operating system and the free disk space. Then find the latest stable Node.js release on the web and tell me the version." > > # or upload the config as a reusable profile instead: > npx omnara profiles create --name machine-assistant --file agent.yaml > ``` > > ```bash REST theme={null} > export AGENT="$( > jq -n --rawfile src agent.yaml '{ > config_source: {source: $src, source_format: "yaml"}, > name: "Machine assistant", > message: "Use your machine to report the operating system and the free disk space. Then find the latest stable Node.js release on the web and tell me the version." > }' | > curl -fsS "$OMNARA_API/orgs/$ORG/projects/$PROJ/agents" \ > -H "Authorization: Bearer $OMNARA_TOKEN" \ > -H "Content-Type: application/json" \ > -d @- | > jq -r '.agent.id' > )" > ``` > > ```typescript SDK theme={null} > import { readFile } from 'node:fs/promises' > > const source = await readFile('agent.yaml', 'utf8') > > const launched = await sdk.createAgent({ > client, > path: { orgID, projectID }, > body: { > config_source: { source, source_format: 'yaml' }, > name: 'Machine assistant', > message: 'Use your machine to report the operating system and the free disk space. Then find the latest stable Node.js release on the web and tell me the version.', > }, > }) > const agentID = launched.data.agent.id > ``` > > **4. Follow the agent and send input** > > The event stream shows the tool calls and the reply as it is generated. Send a follow-up message while the stream runs: > > ```bash CLI theme={null} > npx omnara agents chat {agent-id} > ``` > > ```bash REST theme={null} > curl -N "$OMNARA_API/orgs/$ORG/projects/$PROJ/agents/$AGENT/events/stream?stream_deltas=true" \ > -H "Authorization: Bearer $OMNARA_TOKEN" > > # from a second terminal: > curl -fsS "$OMNARA_API/orgs/$ORG/projects/$PROJ/agents/$AGENT/inputs" \ > -H "Authorization: Bearer $OMNARA_TOKEN" \ > -H "Content-Type: application/json" \ > -d '{"content_blocks": [{"type": "text", "text": "Install that Node.js version on the machine and show me node --version."}]}' > ``` > > ```typescript SDK theme={null} > import { openAgentEventStream } from '@omnara/sdk' > > await sdk.createAgentInput({ > client, > path: { orgID, projectID, agentID }, > body: { > content_blocks: [ > { type: 'text', text: 'Install that Node.js version on the machine and show me node --version.' }, > ], > }, > }) > > for await (const event of openAgentEventStream({ > client, > path: { orgID, projectID, agentID }, > query: { stream_deltas: true }, > })) { > console.log(event) > } > ``` > > To watch the conversation live and send follow-up messages from the browser, open `https://app.omnara.com/projects/{project-id}/agents/{agent-id}`. > [tab] Dashboard > > **1. Open the agent editor** > > In your project, open **Agents** and click **New agent**. > > **2. Select a template** > > In the **Builder** form, select the **Deep researcher** template. It fills the instruction and enables the tools the agent needs: `web_search`, `web_fetch`, `run_command`, the process and machine tools, and `ask_question`. > > **3. Grant and select the model** > > Select your preferred model from the `omnara-openrouter` provider. Omnara automatically [grants](/concepts) models to the default project in your organization. > > **4. Grant and select the machine pool** > > Select `default-pool` as a machine source, which provides your agent with a sandboxed environment. Omnara automatically [grants](/concepts) a machine pool to the default project in your organization. > > **5. Name the agent and launch** > > Click **Create and launch agent**. The dashboard validates the config, launches the agent, and opens the conversation view. ## Next steps Prefer a ready-made agent? The [examples](/examples/overview) deploy with one prompt to your coding agent. > [card] [Concepts](/concepts) > > The object model behind Omnara > [card] [Agents](/agents/overview) > > Launch, update, cancel, and archive agents > [card] [Agent configuration](/agents/configuration) > > Define models, tools, permissions, and machines > [card] [Machine pools](/machines/pools) > > Create machine pools and control their capacity # Built-in tools Source: https://docs.omnara.com/tools/built-in Use built-in tools for web search, commands, machines, integrations, and more Omnara includes built-in tools for common agent tasks. Add ordinary tools by name under `tools:` in the agent's config. Integration tools use names such as `int__engineering__post_message` and the credentials of that project [integration](/integrations/overview). Built-in tools default to `always_allow`, but you can [change the permission](/tools/permissions) for each one. This page explains what each tool does and the inputs it accepts. To retrieve the current catalog programmatically, use [`GET /tool-catalog`](/tools/permissions#check-available-tools). The tools are grouped by what they do: | Group | Tools | | - | - | | Web | `web_search`, `web_fetch` | | Questions | `ask_question` | | Commands | `run_command` | | Processes | `read_process`, `write_process`, `watch_process`, `stop_process`, `list_processes` | | Machines | `create_machine`, `delete_machine`, `list_machines`, `inspect_machine` | | Git | `list_github_repositories` | | Files | `storage_list`, `storage_upload`, `storage_download`, `storage_read`, `storage_search`, `storage_write` | | Integrations | Integration-specific tools; `list_interaction_handlers`, `set_interaction_handler` | | Skills | `skill` | | Tool loading | `tool_search` | A red \* marks required inputs. ## Web ### `web_search` Searches the public web and returns matching URLs, titles, and snippets. | Input | Type | Notes | | - | - | - | | `query`\* | string | | | `num_results` | integer | 1–20; default 5 | | `recency` | enum | `day`, `week`, `month`, `year` | | `domains` | string\[] | Restrict results to these domains | ### `web_fetch` Fetches a public HTTP or HTTPS URL and returns readable content as Markdown or plain text. It cannot access `localhost` or private networks. To reach a service on an attached machine, use `run_command` with `curl`. | Input | Type | Notes | | - | - | - | | `url`\* | string | | | `format` | enum | `markdown` (default), `text` | | `timeout_seconds` | integer | 1–120; default 30 | ## Questions ### `ask_question` Asks a person one or more multiple-choice questions. The agent pauses until the resulting [interaction](/events/interactions) is answered. Omnara automatically adds an **Other** option for free-text responses. | Input | Type | Notes | | - | - | - | | `questions`\* | array | Min 1. Each item requires `prompt` and `options`; each option requires `label`. `multiple` defaults to false. | ## Commands ### `run_command` Runs a shell command on a [machine attached to the agent](/machines/overview#bindings-how-an-agent-holds-a-machine). | Input | Type | Notes | | - | - | - | | `command`\* | string | | | `machine_id` | string | Public ID (`mch_...`) of the [machine](#picking-a-machine) to use; omit when the agent has exactly one | | `shell` | enum | `default`, `sh`, `bash`, `zsh`, `pwsh`, `powershell`, `cmd` | | `cwd` | string | Overrides the binding's working directory | | `wait_ms` | integer | 0–30000 (default 5000); how long to wait for the command to exit before returning. `0` returns as soon as the command starts | | `io_mode` | enum | `pipe` (default) or `pty`. Only `pty` commands take input. See [input](#input) | Every command returns a `process_id` (`prc_…`). `run_command` waits up to `wait_ms` (5 seconds by default) for the command to exit. If it does, the result includes its exit code and output: ```json theme={null} { "process_id": "prc_rwz3jehcyd5n6a2bfgik7mv4qt", "state": "exited", "exit_code": 0, "output": "8c41d2e9f0a3b7c6d5e4f3a2b1c0d9e8f7a6b5c4\n", "next_offset_byte": 41 } ``` Results carry at most 16 KiB of output. Longer output keeps the first 2 KiB in `output` and the rest from the end in `output_tail`; `omitted_offset_byte` and `tail_offset_byte` mark the bytes left out, which [`read_process`](#read_process) can page through. If the command is still running when `wait_ms` passes, the result has `"state": "running"` and the output so far. The command keeps running; the agent can read its output, send input, or stop it with the process tools below, and receives a message when it ends. Agents are told to set a positive `wait_ms` only for commands they expect to finish within that time, and to use `0` for long-running commands or ones of unknown duration, then do other work or end their turn. To wait a set time, an agent can run `sleep` with `wait_ms: 0` and let the exit message wake it. #### Input A `pipe` command gets no input: its stdin is `/dev/null`, so a program that prompts, such as a package manager asking for confirmation, reads end of input and fails at once instead of waiting until `wait_ms` passes. To send input with `write_process`, start the command with `io_mode: pty`. Daemons older than 0.2.15 keep stdin open for `pipe` commands. #### Background processes When a command outlives its `run_command` call, Omnara sends the agent a message when it ends, with its exit status, how long it ran, how much output it wrote, and its last lines of output. The agent does not need to poll with `read_process`; it can do other work or end its turn, and the message wakes it. Like other messages, it waits for any open question or permission request to be answered instead of canceling it. There is no message when the agent terminated the process with `stop_process`. After an interrupt the message still comes, since a program can handle the interrupt and keep running. To hear about specific output while a command runs, such as a development server's ready line or its errors, use [`watch_process`](#watch_process). Set `run_command` to `always_ask` to require approval before execution. The prompt shows the command, machine, shell, and working directory. ### Picking a machine `machine_id` chooses which attached machine runs the command: * With one available machine, omit `machine_id`. * With several, pass the public `machine_id` (`mch_...`) returned by `list_machines`. * With none, attach a machine in the [config](/agents/configuration#machines) or use [`create_machine`](#create_machine). If Omnara cannot choose a machine, the tool returns an error to the agent so it can inspect its machines and retry. ## Processes Use these tools to follow or control commands that are still running. A process moves from `starting` to `running`, then ends as `exited`, `failed`, or `killed`. ### `read_process` Reads process output from a byte offset and can wait for the process to end. | Input | Type | Notes | | - | - | - | | `process_id`\* | string | | | `offset_byte` | integer | ≥ 0; byte position in the output stream. Omit to continue after the output returned by `run_command` or the last `read_process` call that omitted it | | `wait_ms` | integer | 1–30000; wait for the process to end before returning | Without `wait_ms`, `read_process` returns the retained output immediately. With it, the read returns when the process ends or `wait_ms` passes. A `pty` process also returns after one second with no new output, as an interactive program does at a prompt. A read with `offset_byte` returns as soon as there is output at that position. Without `offset_byte`, a read returns at most 16 KiB of output, keeping the start and end like `run_command`. With `offset_byte`, it returns one contiguous page of up to 16 KiB from that position; pass `next_offset_byte` to continue, or `omitted_offset_byte` to read output an earlier result left out. Daemons older than omnarad 0.2.15 return as soon as output arrives and do not keep the end of long output. `run_command` and `read_process` results include `offset_byte` and `next_offset_byte`. To continue from a specific point, pass `next_offset_byte` as `offset_byte`. ### `write_process` Sends input to a running process. The process must have been started with `io_mode: pty`; otherwise the call fails with `process_stdin_unavailable`. | Input | Type | Notes | | - | - | - | | `process_id`\* | string | | | `data`\* | string | Max 128 KiB per write | ### `watch_process` Sends the agent a message with the output lines that match a regular expression, until the watch expires or the process ends. | Input | Type | Notes | | - | - | - | | `process_id`\* | string | | | `pattern` | string | RE2 regular expression, up to 500 bytes, matched against each output line. Required unless `timeout_seconds` is `0` | | `timeout_seconds` | integer | 0–3600 (default 300); when the watch expires. `0` cancels the current watch and takes no `pattern` or `offset_byte` | | `offset_byte` | integer | ≥ 0; byte position to watch from, such as `next_offset_byte` from `run_command`. Omit to watch from the end of the output returned by `run_command` or the last `read_process` call that omitted it, so output written in between is not missed | Matches that arrive together are batched into one message of up to 20 lines, and messages are at least 10 seconds apart. If the agent has not read the watch's last message yet, for example while it waits for an approval, new matches are merged into that message instead of adding another one. When the process ends, matches still pending are sent before its exit message. A process has one watch at a time: calling `watch_process` again replaces the pattern and restarts the timeout, so call it again to keep watching after a watch expires. When a watch expires, the agent gets a message saying so, so it can watch again or move on. Calling `watch_process` with `timeout_seconds: 0` cancels the watch without that message; matches it already found are still sent. For example, `run_command` starts a development server, then `watch_process` with `pattern: "listening on|error"` tells the agent when it is ready or fails. Watches suit rare lines the agent wants to be told about; it reads the surrounding output with `read_process`. A pattern that matches frequent lines, such as progress output, sends a message every 10 seconds, so the agent should narrow or cancel a watch that reports too often. `watch_process` needs omnarad 0.2.15 or later. ### `stop_process` Interrupts a process gracefully or terminates it. Repeating the call after the process has stopped is safe. | Input | Type | Notes | | - | - | - | | `process_id`\* | string | | | `mode`\* | enum | `interrupt` (SIGINT-style) or `terminate` | ### `list_processes` No inputs. Returns the agent's active processes with their state, command label, and working directory. ### When machines misbehave Agents remain observable when machine state changes: * **The machine disconnects** — the agent can wait for it to reconnect or continue elsewhere. Retained output and process state remain available. * **Output exceeds retention** — `read_process` returns `truncated: true` when older output has been dropped. For large output, write it to a file and read only what you need. * **The machine is deleted** — its processes end and its binding is released. ## Machines These tools let an agent inspect its BYO and pool-backed machines, and create or delete pool-backed machines. Its [config](/agents/configuration#machines) and [machine or pool grants](/machines/overview#who-may-use-a-machine) determine which machines it can use and how many pool-backed machines it can create. ### `create_machine` Creates a machine from an available pool. Use `list_machines` to discover pools, then pass the selected `machine_pool_id`. You can omit the ID when only one pool is available. Pool names are for display; renaming a pool does not change which pool an ID selects. Optional `cpu` and `memory_mb` override the defaults where supported, within pool and project limits. ### `delete_machine` Deletes a pool-backed machine. Requires `machine_id`. ### `list_machines` / `inspect_machine` `list_machines` lists the agent's BYO and pool-backed machines, plus pools from its config that have an active project grant. Pools are included even when they have no machines yet. Each pool includes its `machine_pool_id`, `machine_pool_name`, and configured description. Pools also expose supported resource overrides, effective defaults, and CPU/memory limits. `inspect_machine` shows whether a specific machine is ready to run commands; provide `machine_id` when the agent has more than one. Machine results include `cpu` and `memory_mb` when known, identify `source_kind` and `binding_kind`, and include `machine_pool_id` for pool-backed machines. Only pool bindings can be passed to `delete_machine`. `list_machines` accepts `offset` (default 0) and `limit` (1–100, default 50). When the result has `has_more: true`, pass `next_offset` as `offset` in the next call to continue listing. Pools are listed first, followed by machines. If `list_machines` is disabled, provide the agent with any needed pool IDs, supported resource overrides, and limits in its instructions. ## Git ### `list_github_repositories` Lists the GitHub repositories the agent has access to through its [Git credentials](/integrations/github#git-credentials). Each repository has a `full_name`, such as `acme/app`. `can_push` is `true` when the integration has **Let agents push code** enabled and the credentials aren't `read_only`. Omnara adds this tool when a config has `git_credentials`. `list_github_repositories` accepts `offset` (default 0) and `limit` (1–100, default 50). When the result has `has_more: true`, pass `next_offset` as `offset` in the next call to continue listing. A page can hold fewer than `limit` repositories even when more remain. ## Files `path` addresses files stored in Omnara, not files on an attached machine. `/artifacts` is the destination for creating an artifact, `/artifacts/` identifies an existing artifact, and `/memory//` identifies a file in an [attached memory store](/agents/configuration#memory-stores). When compiling a config with enabled tools or MCP servers, Omnara adds missing `storage_list`, `storage_read`, and `storage_search` entries to the compiled config, leaving the source unchanged. Attaching a memory store also adds these retrieval tools; a `read_write` attachment adds `storage_write`. Explicit tool permissions and `enabled: false` settings take precedence. These tools do not require an attached machine. Integration-contributed tools are compiled with the config too. ### `storage_list` Lists matching files and directories without reading their contents. | Input | Type | Notes | | - | - | - | | `pattern`\* | string | Path pattern under literal `/memory` or `/artifacts`; globs are allowed only below the root | | `offset` | integer | ≥ 0; default 0. Use `next_offset` from the previous result with the same pattern | | `limit` | integer | 1–100; default 50 | Use `/artifacts/*` for the agent's artifacts, `/memory/*` for attached stores and their descriptions, or `/memory/**` to list paths across attached stores recursively. For example, `/memory/engineering/**/*.md` finds Markdown files across directories. `*` matches within a path segment, `**` spans directory levels, and `?` matches one character. Artifact patterns such as `/artifacts/*.pdf` match filenames and return `/artifacts/` paths for reading or downloading. An exact `/artifacts/` selects one artifact; wildcards match filenames, not IDs, and a filename can match multiple artifacts. Artifacts are ordered newest first and include creation time and content type. Results include hidden entries. While `has_more` is true, continue with `next_offset`; narrow the pattern if the listing exceeds its resource limit. ### `storage_upload` Use `storage_upload` to copy a file from an attached machine into Omnara. Uploads to `/artifacts` create artifacts, making files such as generated PDFs, images, or reports available for the user to view or download. Set `path` to `/artifacts` or `/memory//` and pass the regular machine file's relative, absolute, or `~`-relative path as `source`; provide `machine_id` when the agent has more than one machine. Files may be up to 10 MiB. Artifacts must be non-empty; memory files may be empty. The upload process has a 30-second execution timeout. On success, an artifact appears in the conversation and the durable tool result contains its `path` and a `media_ref` that clients can use to display or download it. The returned `path` can be passed directly to `storage_download`; `source` and `destination` refer to paths on the selected machine. The artifact's contents are not added to later model context unless `view` is true and the file is a PNG, JPEG, GIF, or WebP image; set `view` to look at a screenshot or other image in the same call. Other files uploaded with `view` succeed with a note that they weren't shown. To deliver artifacts through an integration, use its qualified posting tool, such as `int__engineering__post_message`, and follow its attachment schema. A memory upload requires write access and returns its `path`. Replacing an existing file requires the agent to have read, written, uploaded, or downloaded it since its last change, like `storage_write`; `force: true` skips that check. On conflict, read or download the latest file and reapply your changes before retrying. Uploading identical contents always succeeds without changing the file. Artifact uploads do not accept `force`, and memory uploads do not accept `view`. ### `storage_download` Use `storage_download` to copy a file stored in Omnara to an attached machine. Downloads support `/artifacts/` and `/memory//`; provide `machine_id` when the agent has more than one machine. Set `path` to the stored file's path and `destination` to the exact machine file path. Relative and `~`-relative destinations are supported, the parent directory must already exist, and an existing destination is replaced atomically. A memory download counts as reading the file, so the agent can edit the copy and upload it back. The download process has a 120-second execution timeout. ### `storage_read` Reads a text or image file at `/artifacts/` or `/memory//`. Text must be UTF-8 without NUL bytes. Artifacts may be up to 48 MiB; memory files may be up to 10 MiB. Readability is determined from the file's bytes, regardless of its declared content type. PNG, JPEG, GIF, and WebP images are returned for the model to view, with `path`, `content_type`, and `size_bytes` instead of paging fields; paging inputs are still validated but don't apply to images. Memory images are preserved as immutable artifacts when read and appear in the agent's artifact listings. | Input | Type | Notes | | - | - | - | | `path`\* | string | Exact artifact or memory file path | | `offset_line` | integer | ≥ 1; default 1 | | `limit_lines` | integer | 1–200; default 100 | | `offset_char` | integer | ≥ 0; default 0 in character mode; counts Unicode code points | | `limit_chars` | integer | 1–4096; default 512 in character mode | Reading defaults to line mode. Supplying either character input selects character mode; do not combine line and character inputs. Each text response contains at most 4 KiB of file text, together with `size_bytes`, `content_type`, and `has_more`. When `has_more` is true, continue using the returned `next_offset_line` or `next_offset_char`. A line longer than one page returns its first chunk and a character offset; switch to character mode to continue. Reading past the end returns empty content with `has_more: false`. If a memory file changed since the agent last read, wrote, uploaded, or downloaded it, the result includes a note with the file's update time and the time of that read. ### `storage_write` Creates or edits a memory text file in a writable store attached with `read_write` access. Returns `path`. | Input | Type | Notes | | - | - | - | | `path`\* | string | Exact `/memory//` destination | | `content` | string | Text to create or replace the file with; empty text is allowed | | `script` | string | GNU sed script using extended regex, at most 64 KiB; transforms an existing file | | `append` | boolean | Append `content` exactly, without adding a newline; default false. Creates missing files. | | `force` | boolean | Overwrite even if the file changed since the agent's last read, or was never read; default false | Supply exactly one of `content` or `script`; `append` only applies to `content`. Text must be UTF-8 without NUL bytes, at most 10 MiB. Scripts cannot read other files, write files, or run commands, and run with resource limits. Changing an existing file requires the agent to have read or written it, and fails if the file changed since then; read the file again before retrying. Creating a file needs no prior read. ### `storage_search` Searches text in one artifact or in memory files matching a path or glob. Binary files may be skipped when searching with a glob. | Input | Type | Notes | | - | - | - | | `path`\* | string | For artifacts, one exact `/artifacts/` path. For memory, an exact file path or a glob such as `/memory/notes/**/*.md`. | | `args`\* | string\[] | Ripgrep options and `-e` patterns; for example `["-i", "-C", "2", "-e", "deploy"]` | | `limit` | integer | 1–100 entries across all files; default 20 | | `offset_line` | integer | Starting line, default 1; only for exact-file content searches | Supported options are `-e` (repeatable patterns), `-F` (literal), `-i` (ignore case), `-w` (whole word), `-x` (whole line), `-v` (invert), `-U` (multiline), `-l` (matching paths), `-c` (counts), and `-A`/`-B`/`-C` (0–5 context lines). Patterns total at most 1024 UTF-8 bytes. Other options and file operands are rejected. Content results are a sequence of match and context records with paths, original line numbers, and bounded snippets. Context does not count toward `limit`; counts in `-c` results are not capped by it. Context may be cut off at the response limit. Narrow the query when truncated. ### Large tool results For `web_fetch`, `web_search`, `skill`, MCP tools, and custom tools, results containing text or structured data trigger overflow handling when their serialized content exceeds 50 KiB. Omnara saves the oversized content as a text or JSON artifact and returns its `path`, a preview of up to 8 KiB, `size_bytes`, `content_type`, and `truncated: true`. Saved overflow artifacts are limited to 48 MiB. The overflow marker's `truncated` field describes the shortened inline preview. In `web_fetch` metadata, `truncated` reports whether the upstream extractor shortened the fetched content; it can be `false` even when the overflow marker is `true`. For example, a large `web_fetch` result provides an `/artifacts/` path. The agent can search that path for a phrase with `storage_search`, then read the surrounding lines with `storage_read`. Visible overflow artifacts also appear as downloadable attachments in the conversation. Hidden results remain hidden. Explicitly disabling both retrieval tools does not disable overflow handling. The agent still receives the preview and path, but cannot use these tools to retrieve the saved content. Existing media references remain inline so their attachments remain available to the model and clients. Consequently, 50 KiB is an overflow threshold, not a hard limit on the rewritten result: many retained media references can still exceed it. Media-only results are not offloaded. ## Integration tools Select [integration tools](/integrations/overview) by qualified name, such as `int__engineering__post_message`. An integration owns its credentials and defines its tools' argument schemas and conversation requirements. Credential access, live integration state and ordinary permission govern execution. Ordinary model output remains in Omnara. Subscriptions control incoming forwarding independently of enabled tools. Manage them from the integration's [Conversations list or subscriptions API](/integrations/overview#stop-forwarding). ### `list_interaction_handlers` / `set_interaction_handler` Select these tools explicitly under `tools`, or use an integration launcher that supplies an interaction handler and adds them, preserving explicit tool settings. They are not global defaults. `list_interaction_handlers` accepts `offset` and `limit`, returning handler keys, descriptions, eligible destinations and argument schemas. Its current selection is included independently of pagination. No eligible handlers means an empty list. `set_interaction_handler` takes a `handler` key and `args` matching its schema. An integration may infer the destination from its agent state; in that case its schema accepts empty arguments. Use `{"handler":null,"args":{}}` for dashboard only. Pass `auto_select: false` to pin the choice, or `true` to enable automatic selection; omitting it preserves the current mode. In automatic mode, the last eligible content input admitted to a turn selects its handler, or clears the choice if none applies. Dashboard and API messages therefore select dashboard-only delivery. Internal agent reports/messages and ordinary cron instructions preserve selection. An explicit integration origin takes precedence over that exception, including a scheduled integration thread's initial input. Existing prompts keep their captured destination. Both tools report the current automatic-selection mode. ## Subagents These tools are added automatically when the config declares [`subagents`](/agents/configuration#subagents). Each subagent is an ordinary agent with a `parent_agent_id`. ### `spawn_agent` Starts a subagent from a configured `subagents` key with a clean context. Requires `agent` (the key) and `task`; `name` is an optional display label. Returns immediately with the subagent's `agent_id`, which the other subagent tools take. The subagent's final answer arrives later as a message from it. ### `read_agent` Reads a subagent's timeline by `agent_id` in the same shape as the [turns API](/api-reference/endpoints/events/list-turns). Without `turn_id` it returns the newest turns first, each with its opening events and latest event; `before_turn_sequence` pages older turns and `limit` defaults to 10. With `turn_id` it returns that turn's events, newest first; `before_sequence` pages older events and `limit` defaults to 20. Each result carries `next_before_turn_sequence` or `next_before_sequence` for the next page. ### `send_agent_message` Delivers `message` to the subagent identified by `agent_id` as a steering input. The subagent reads it once its current model call and tool batch finish, so running work is not stopped, but any open question or permission request is canceled. Parents cannot answer a subagent's questions; those wait for a human. The reply arrives as a message from the subagent. ### `stop_agent` Cancels the current work of the subagent identified by `agent_id` and leaves it idle, so `send_agent_message` can resume it later. With `archive: true` it also archives the subagent and its descendants, after which it can no longer be messaged. ### `list_agents` Lists the agent's subagents with their `agent_id`, name, key, state, and last activity. ## Skills ### `skill` Loads an attached skill. Requires its [skill](/tools/skills) `name`. The tool returns the skill's instructions and, when the agent has machines, installs its supporting files there. ## Tool loading ### `tool_search` Added automatically when any tool is [deferred](/agents/configuration#deferred-tools). Searches deferred tools by name, description, and argument names, and loads the matches so the model can call them. | Input | Type | Notes | | - | - | - | | `pattern`\* | string | Case-insensitive regular expression, at most 200 characters | | `max_results` | integer | 1–50; default 5 | > [!NOTE] > > Full inputs and defaults, live from your deployment: [Get tool catalog](/api-reference/endpoints/configs-and-profiles/get-tool-catalog). # Custom tools Source: https://docs.omnara.com/tools/custom Declare a tool the model can call, execute it in your own system, and post the result back A custom tool is how an agent reaches systems only you can touch — your ticket tracker, your database, your deploy pipeline. The division of labor is clean: **you describe the tool, the model decides when to call it, and your code does the work**. Omnara sits in the middle holding the call durably until you answer it. ```mermaid theme={null} sequenceDiagram participant M as Model participant O as Omnara participant W as Your worker M->>O: tool_call create_ticket {...} Note over O: state: ready W->>O: GET /tool-calls?state=ready O-->>W: the pending call W->>W: create the ticket W->>O: POST /tool-calls/{id}/result O->>M: tool_result → agent continues ``` Because the call is a durable event, your worker can crash, redeploy, or take minutes to respond — the agent simply waits, and nothing is lost. ## Declare the tool In the agent config, a custom tool is a name, a description, and a JSON Schema for its input. Write the description as clear instructions: explain when to use the tool, what it does, and any important limits. ```yaml theme={null} tools: create_ticket: type: custom description: >- File a ticket in the internal tracker. Use for actionable engineering work discovered during the conversation; keep titles under 80 chars. input_schema: type: object properties: title: { type: string } body: { type: string } priority: { type: string, enum: [low, normal, urgent] } required: [title] ``` Custom tools default to `always_allow`; set `permission.mode: always_ask` to put a human between the model and your system ([Tools & permissions](/tools/permissions)). ## Find calls waiting for you `GET /tool-calls` lists an agent's tool calls, filterable by `state` and `type`. Your worker polls for ready custom calls: ```bash theme={null} curl "$OMNARA_API/orgs/$ORG/projects/$PROJ/agents/$AGENT/tool-calls?state=ready&type=custom" \ -H "Authorization: Bearer $OMNARA_TOKEN" ``` ```json theme={null} { "data": [ { "id": "tcl_wz3jehcyd5n6a2bfgik7mv4qtr", "turn_id": "trn_trwz3jehcyd5n6a2bfgik7mv4q", "provider_call_id": "call_9f3k", "name": "create_ticket", "input": { "title": "Flaky retry logic in payments worker", "priority": "normal" }, "type": "custom", "state": "ready", "created_at": "2026-08-03T20:15:09Z" } ], "next_cursor": null } ``` `ready` means the permission check has passed and the call is waiting for your result. A custom call using `always_ask` remains in `awaiting_permission` until someone responds to its [interaction](/events/interactions). After you submit a result, it moves to `completed`. > [!TIP] > > To avoid continuous polling, which counts against your [rate limits](/api/overview#rate-limits), connect to the agent's [event stream](/events/streaming), then query `GET /tool-calls?state=ready&type=custom` for already-ready calls and watch for future `tool_call_update` frames whose `state` is `ready`. Repeat the query after reconnecting because lifecycle updates are not replayed. Subagents can call custom tools too: the parent's stream carries their `tool_call_update` frames with the subagent's `agent_id`, and `include_subagents=true` on the query returns their calls. > > You can also configure an [Event webhook](/agents/configuration#event-webhooks) to trigger custom-tool execution when a `tool_call_update` arrives with `state: ready`. See the runnable [custom-tool webhook example](https://github.com/omnara-ai/omnara/tree/main/examples/custom-tool-webhook) for signature verification and duplicate handling. ## Submit the result Post the outcome with content blocks — text, inline media, or structured data: ```bash theme={null} curl "$OMNARA_API/orgs/$ORG/projects/$PROJ/agents/$AGENT/tool-calls/tcl_wz3jehcyd5n6a2bfgik7mv4qtr/result" \ -H "Authorization: Bearer $OMNARA_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "outcome": "succeeded", "content_blocks": [ { "type": "structured_data", "value": { "ticket_id": "ENG-4212", "url": "https://tracker.acme.dev/ENG-4212" } } ] }' ``` `201` returns both the completed call and the durable `tool_result` event now in the agent's timeline: ```json theme={null} { "tool_call": { "id": "tcl_wz3jehcyd5n6a2bfgik7mv4qtr", "name": "create_ticket", "type": "custom", "state": "completed", "outcome": "succeeded", "completed_at": "2026-08-03T20:15:31Z", "...": "..." }, "tool_result": { "event_id": "evt_5n6a2bfgik7mv4qtrwz3jehcyd", "agent_id": "agt_v4qtrwz3jehcyd5n6a2bfgik7m", "tool_call_id": "tcl_wz3jehcyd5n6a2bfgik7mv4qtr", "outcome": "succeeded", "content_blocks": [ { "type": "structured_data", "value": { "ticket_id": "ENG-4212", "...": "..." } } ], "created_at": "2026-08-03T20:15:31Z" } } ``` The agent wakes and continues its turn with your result in context. If your system failed, submit `outcome: "failed"` with a text block explaining why — a failed tool call is information the model can act on, not an error you should swallow. Result text, structured-data string keys and values, and metadata must not contain U+0000. **The first result wins.** Only `ready` custom calls accept results; a second submission for the same call, or a result for a built-in or MCP call, returns `409`: ```json theme={null} { "error": "custom tool call is not ready for a result", "code": "conflict" } ``` That makes result submission naturally safe to retry from multiple workers — losers of the race get a clean `409`, not a corrupted timeline. > [!NOTE] > > Full schema and playground: [List tool calls](/api-reference/endpoints/agents/list-tool-calls) · [Submit a result](/api-reference/endpoints/agents/submit-a-result-for-a-ready-custom-tool-call). # MCP servers Source: https://docs.omnara.com/tools/mcp Connect MCP servers and configure their tools, permissions, and authentication [MCP (Model Context Protocol)](https://modelcontextprotocol.io) is an open standard for connecting tools to models. Add a server such as Linear, Notion, or your own internal service to an agent config, and Omnara discovers its tools and makes them available to the model. ## Declaring a server Each entry under `mcp:` defines one server: ```yaml theme={null} mcp: linear: url: https://mcp.linear.app/mcp auth: type: oauth secret_id: sec_gik7mv4qtrwz3jehcyd5n6a2bf permission: mode: always_ask tools: get_issue: permission: mode: always_allow delete_issue: enabled: false internal-docs: url: https://mcp.internal.acme.dev/mcp auth: type: bearer secret_id: sec_7jehcyd5n6a2bfgik7mv4qtrwz default_enabled: false # opt-in: only listed tools are exposed tools: search_docs: enabled: true ``` | Field | Notes | | - | - | | `url` | The server's MCP endpoint. | | `auth.type` | `bearer` (a token in a [generic secret](/organization/secrets)), `oauth` (an `oauth_token_set` secret), or `sigv4` (an `aws_credentials` secret). Omit for unauthenticated servers. | | `auth.secret_id` | The `sec_…` reference. The config never contains the credential itself. | | `auth.service` | SigV4 signing service. Required for `sigv4`. | | `auth.region` | AWS signing region. Required for `sigv4`. | | `permission` | Server-wide default mode; per-tool `permission` overrides it. MCP tools default to `always_ask` — third-party code asks first. | | `default_enabled` | `false` disables tools by default. Set `enabled: true` for each tool you want to expose. | | `deferred` | `true` keeps the server's tools out of the model's context until `tool_search` loads them; per-tool `deferred` overrides it. See [Deferred tools](/agents/configuration#deferred-tools). | ### How MCP tools are named MCP tool names combine the server and tool: `mcp__{server}__{tool}`. For example, Linear's `get_issue` tool appears as `mcp__linear__get_issue`. These names appear in `model_output` tool calls and in [`GET /tool-calls`](/tools/custom#find-calls-waiting-for-you) with `type: "mcp"`. Server names cannot contain underscores, and the combined name must fit within 64 characters. ## Protocol versions and tool caching * Omnara supports both stateless (`2026-07-28`) and session-based (`2025-11-25` and earlier) MCP servers. Stateless MCP servers use the cache TTLs returned by the server with a minimum freshness window of five minutes; capabilities and tools are cached keyed on org, server, and authentication information (if present). ## Bearer auth For servers that take a static token, store the token as a generic secret and reference it: > [tab] API > > The examples assume the client, `$ORG`/`orgID`, and `$PROJ`/`projectID` setup from the [quickstart](/quickstart). > > ```bash CLI theme={null} > omnara secrets create \ > --owner-kind project \ > --owner-project-id proj_7mv4qtrwz3jehcyd5n6a2bfgik \ > --name internal-docs-mcp-token \ > --material-kind generic \ > --material-value "mcp-token-..." > ``` > > ```bash REST theme={null} > curl "$OMNARA_API/orgs/$ORG/secrets" \ > -H "Authorization: Bearer $OMNARA_TOKEN" \ > -H "Content-Type: application/json" \ > -d '{ > "owner": { "kind": "project", "project_id": "'"$PROJ"'" }, > "name": "internal-docs-mcp-token", > "material": { "kind": "generic", "value": "mcp-token-..." } > }' > ``` > > ```typescript SDK theme={null} > const secret = await sdk.createSecret({ > client, > path: { orgID }, > body: { > owner: { kind: 'project', project_id: projectID }, > name: 'internal-docs-mcp-token', > material: { kind: 'generic', value: 'mcp-token-...' }, > }, > }) > console.log(secret.data.id) > ``` > [tab] Dashboard > > Open **Secrets** — the **User** or **Organization** tab with **All projects** selected in the project switcher, or the **Project** tab inside a project — and click **New secret**. Name it, choose the **Generic** kind, and paste the token. You can pick projects under **Share with** in the same dialog. The response's `id` (`sec_…`) goes in `auth.secret_id` with `type: bearer`. Rotation is a [new secret version](/organization/secrets#rotate-a-secret) — configs keep referencing the same ID. For a complete example you can deploy, using PostHog's MCP server with tool filtering and optional Slack delivery and daily schedule, see the [PostHog analytics agent](/examples/posthog-analytics-agent). ## AWS Signature Version 4 auth For a SigV4-protected MCP server, create an **AWS credentials** secret in the dashboard, share it with the project, and select **AWS Signature V4** for server authentication: ```yaml theme={null} mcp: aws: url: https://aws-mcp.us-east-1.api.aws/mcp auth: type: sigv4 secret_id: sec_7jehcyd5n6a2bfgik7mv4qtrwz service: aws-mcp region: us-east-1 ``` Omnara optionally assumes the role stored with the credentials, then signs each request with AWS Signature Version 4 using the configured service and region. It does not modify the MCP request body or create a bearer token. `auth.region` is the MCP endpoint's signing region; it does not limit the AWS resource regions selected in tool calls. Omnara does not refresh a session token stored in the AWS credentials secret, so rotate that secret before the token expires. ## Connect with OAuth For servers such as Linear, Omnara handles OAuth registration, authorization, and token exchange. It stores the resulting credentials in an `oauth_token_set` secret that your config references. > [tab] API > > The CLI command runs the whole flow — it opens the server's authorization page in your browser, waits for approval, and prints the created secret's ID (`--no-browser` prints the authorization URL instead of opening it). The REST API and the SDK start the flow with one request and return an authorization URL to open yourself: > > ```bash CLI theme={null} > omnara secrets mcp-oauth \ > --owner-kind project \ > --owner-project-id proj_7mv4qtrwz3jehcyd5n6a2bfgik \ > --name linear-mcp \ > --mcp-url https://mcp.linear.app/mcp > ``` > > ```bash REST theme={null} > curl "$OMNARA_API/orgs/$ORG/secrets/mcp-oauth" \ > -H "Authorization: Bearer $OMNARA_TOKEN" \ > -H "Content-Type: application/json" \ > -d '{ > "owner": { "kind": "project", "project_id": "'"$PROJ"'" }, > "name": "linear-mcp", > "mcp_url": "https://mcp.linear.app/mcp", > "return_to": "/projects/'"$PROJ"'/secrets" > }' > ``` > > ```typescript SDK theme={null} > const start = await sdk.startSecretMcpoAuth({ > client, > path: { orgID }, > body: { > owner: { kind: 'project', project_id: projectID }, > name: 'linear-mcp', > mcp_url: 'https://mcp.linear.app/mcp', > return_to: `/projects/${projectID}/secrets`, > }, > }) > console.log(start.data.authorization_url) > ``` > > ```json theme={null} > { > "authorization_url": "https://linear.app/oauth/authorize?client_id=…&state=…", > "expires_at": "2026-08-03T20:28:00Z" > } > ``` > > Open `authorization_url` in a browser and approve access. Omnara reads the server's OAuth metadata, registers a client when supported, exchanges the authorization code, and stores the result as an `oauth_token_set` secret. You can also pass an existing client ID and secret (`--client-id` and `--client-secret` on the CLI, `client_id` and `client_secret` in the start request). > > The browser returns to the dashboard-relative `return_to` path with `?mcp_oauth=success&secret_id=sec_…` appended. Absolute return URLs are rejected. Use that secret ID with `auth.type: oauth`. The authorization link is single-use and expires in 10 minutes. > > To authorize a server and attach it to an existing config in one step, use `omnara agents mcp-add {agent-id} --server-name linear --mcp-url …` or `omnara profiles mcp-add {agent-profile-id} --server-name linear --mcp-url …`. Both create the OAuth secret and add the `mcp:` entry to the agent's or profile's config for you. > [tab] Dashboard > > On any Secrets page, click **New secret**, pick the **MCP OAuth secret** kind, name it, and enter the MCP server URL. **Authorize and Create Secret** sends you to the server's authorization page; approve, and you land back in the dashboard with the secret saved. > > The secret's row action **Copy MCP config** then puts a ready-to-paste `mcp:` YAML block — the server URL plus `auth.type: oauth` and the `secret_id` — on your clipboard. Re-running the flow with the same owner and name creates a new *version* of the same secret, so re-authorizing never forces a config change. Troubleshooting: * If the MCP server doesn't actually require auth, the start call returns `409` — declare it without an `auth` block instead. * If a token expires and cannot be refreshed, affected tool calls return `mcp_connection_failed`. Re-run the OAuth flow to create a fresh secret version; the config can keep using the same secret ID. > [!NOTE] > > Full schema and playground: [Start MCP OAuth for a new secret](/api-reference/endpoints/secrets/start-mcp-oauth-for-a-new-secret). # Tools & permissions Source: https://docs.omnara.com/tools/permissions Control which tools an agent can use and which calls require approval Tools let an agent search the web, run commands, call external services, and more. An agent can use only the tools listed in its current [config](/agents/configuration). Changing that set requires an explicit [config update](/events/sending-input#change-the-config-mid-conversation), so you can always see exactly what the agent has access to. Omnara has exactly three kinds of tools, distinguished by *who executes them*: | Kind | Executed by | Declared as | Default permission | | - | - | - | - | | **Built-in** | Omnara | A name from the [built-in set](/tools/built-in), e.g. `web_search` | `always_allow` | | **MCP** | A [Model Context Protocol](https://modelcontextprotocol.io) server you point at | An entry under `mcp:`, exposed as `mcp__{server}__{tool}` | `always_ask` | | **Custom** | **Your own system**, via the API | `type: custom` with a description and input schema | `always_allow` | ## Permission modes Each tool has one of three permission modes. For MCP tools, you can set a default for the server and override it for individual tools: | Mode | What happens on each call | | - | - | | `always_allow` | Runs immediately. No human involved. | | `always_ask` | The call pauses in `awaiting_permission` and a **permission [interaction](/events/interactions)** opens. A human approves or denies; the agent continues either way, knowing the outcome. | | `always_deny` | The call is refused and the model is told so. Useful for explicitly fencing off one tool from a server that's otherwise allowed. | ```yaml theme={null} tools: web_search: {} # built-in, default always_allow run_command: permission: mode: always_ask # every command needs approval mcp: linear: url: https://mcp.linear.app/mcp permission: mode: always_ask # server-wide default tools: get_issue: permission: mode: always_allow # reads are fine delete_issue: permission: mode: always_deny # never, even with approval ``` A permission mode applies to every call. Changing it requires creating a new config and [updating the agent](/events/sending-input#change-the-config-mid-conversation). > [!NOTE] > > Provider tools such as `int__engineering__post_message` use the ordinary permission modes. > They use the named project [integration](/integrations/overview) and its provider credentials. > Approval prompts show the destination alongside the action. Interaction > handlers are independent: an agent can request approval through a handler without > having that integration's sending tools. ## What an `always_ask` call looks like When an agent calls a tool with `always_ask`, its turn pauses and a permission interaction opens. Until someone approves or denies it, the tool call has state `awaiting_permission`. [Approvals & questions](/events/interactions) shows the form and how to respond. ## Check available tools `GET /tool-catalog` lists the built-in tools available in your deployment, including each tool's description, default permission, and supported modes: ```bash theme={null} curl "$OMNARA_API/tool-catalog" -H "Authorization: Bearer $OMNARA_TOKEN" ``` The response also includes the supported modes and defaults for custom and MCP tools in `custom_tool_permissions` and `mcp_tool_permissions`. Use it to build or validate configs against your deployment's current tool catalog. ## Next > [card] [Built-in tools](/tools/built-in) > > Available tools, with inputs and defaults > [card] [Custom tools](/tools/custom) > > Declare a tool, execute it in your system, post the result back > [card] [MCP servers](/tools/mcp) > > Connect third-party tool servers, including the OAuth flow > [card] [Skills](/tools/skills) > > Versioned instruction packages agents load on demand # Skills Source: https://docs.omnara.com/tools/skills Versioned packages of instructions and files that agents load on demand A skill packages reusable instructions and optional files for agents. You upload it once, attach its ID to agent configs, and the model calls the built-in [`skill` tool](/tools/built-in#skill) when it needs the instructions. Skills are: * **Lazy** — only the name and description enter the model's context until it loads the skill. * **Versioned** — uploading the same skill again creates a new revision under the same skill ID. * **Shared** — one skill can be granted to multiple projects and attached to many agents. ## Upload a skill Upload a `.zip` or `.tar.gz` archive with one top-level directory: ```text theme={null} release-notes/ ├── SKILL.md └── templates/ └── example.md ``` `SKILL.md` starts with the skill's name and description: ```md theme={null} --- name: release-notes description: Use when writing release notes for Acme. --- Follow the Acme release-note format in templates/example.md. ``` Write the description as a trigger—“Use when…”—because it helps the model decide when to load the skill. | Rule | Limit | | - | - | | `name` | Required. Lowercase letters and digits in hyphen-separated segments (`release-notes`), up to 64 characters. Must match the archive's top-level directory. | | `description` | Required, up to 1,024 characters. | | `SKILL.md` | Must sit directly inside the top-level directory, up to 256 KB. | | Archive | Up to 25 MB and 1,000 entries. Symlinks are rejected; macOS metadata (`__MACOSX/`, `.DS_Store`, `._*`) is ignored. | > [tab] API > > The examples assume the client, `$ORG`/`orgID`, and `$PROJ`/`projectID` setup from the [quickstart](/quickstart). > > `POST /skills` takes multipart form data: an `owner` and the `archive` itself. When calling the API directly, send `owner` as a JSON part (`Content-Type: application/json`); the SDK and CLI do this for you. The CLI's `--archive` takes a `.zip` or `.tar.gz` file, or a skill directory that it packs for you: > > ```bash CLI theme={null} > omnara skills create --owner-kind org --archive release-notes/ --json > ``` > > ```bash REST theme={null} > curl "$OMNARA_API/orgs/$ORG/skills" \ > -H "Authorization: Bearer $OMNARA_TOKEN" \ > -F 'owner={"kind": "org"};type=application/json' \ > -F "archive=@release-notes-skill.zip" > ``` > > ```typescript SDK theme={null} > import { readFile } from 'node:fs/promises' > > const bytes = await readFile('release-notes-skill.zip') > > const skill = await sdk.createSkill({ > client, > path: { orgID }, > body: { > owner: { kind: 'org' }, > archive: new File([bytes], 'release-notes-skill.zip'), > }, > }) > ``` > > ```json theme={null} > { > "id": "skl_ik7mv4qtrwz3jehcyd5n6a2bfg", > "org_id": "org_k7mv4qtrwz3jehcyd5n6a2bfgi", > "owner": { "kind": "org" }, > "name": "release-notes", > "revision_id": "skr_d5n6a2bfgik7mv4qtrwz3jehcy", > "revision": 1, > "description": "Use when writing release notes for Acme.", > "skill_md": "---\nname: release-notes\ndescription: Use when writing release notes for Acme.\n---\n\nFollow the Acme release-note format in templates/example.md.", > "created_at": "2026-08-03T20:31:04Z", > "updated_at": "2026-08-03T20:31:04Z" > } > ``` > > For a project- or user-owned skill, set the owner accordingly: `--owner-kind project --owner-project-id proj_…` or `--owner-kind user` with the CLI, or `{"kind": "project", "project_id": "proj_…"}` / `{"kind": "user"}` as the `owner`. > [tab] Dashboard > > 1. With **All projects** selected in the project switcher, open **Skills** and pick the **User** or **Organization** tab, or open **Skills** inside a project and stay on its **Project** tab. The page determines who owns the skill. > 2. Click **Create skill**. > 3. Drop files or a folder onto the dialog, or use **Choose files** / **Choose a folder**. You can also switch to the **SKILL.md** tab and write the skill in the editor. > > The dashboard accepts a `.zip`, a `.tar.gz`, a loose `SKILL.md`, or a folder, and zips folders in the browser before uploading. A folder or archive holds one skill when `SKILL.md` sits at its top, or several skills when each subfolder has its own `SKILL.md`; deeper `SKILL.md` files are not picked up. Folders skip `node_modules` and dot-directories such as `.git`; archives upload every file. The dashboard names each skill's top-level directory after its frontmatter `name`, so the folder name does not have to match. > > Frontmatter problems are flagged before anything uploads. When you upload several skills, reuse an existing name, or include two skills with the same name, a review step lists each skill as new, a new revision (for example `v2 → v3`), or skipped, and uploads only after you confirm. Skills upload one at a time; if some fail, **Retry** re-sends only those. The owner (`org`, `project`, or `user`) decides who manages the skill and cannot change. A project can attach only skills it owns or has been granted—even skills owned by its organization. Uploading again with the same owner and name creates the next revision: the `skl_` ID stays the same and the `skr_` revision ID changes. Attached agents resolve the latest revision at each model call, so they do not need to be relaunched. ## Update a skill Each update creates the next revision; the skill's name cannot change. > [tab] API > > `POST /skills/{skillID}` takes multipart form data with exactly one of: > > * `skill_md` — replacement `SKILL.md` text. Every other file in the current revision is kept. > * `archive` — a new `.zip` or `.tar.gz` that replaces all of the skill's files. > > ```bash theme={null} > curl "$OMNARA_API/orgs/$ORG/skills/skl_ik7mv4qtrwz3jehcyd5n6a2bfg" \ > -H "Authorization: Bearer $OMNARA_TOKEN" \ > -F "skill_md= ``` > > The CLI has no update command. Running `omnara skills create` again with the same owner and skill name uploads the next revision. > [tab] Dashboard > > Click the skill's name to open its page. Edit `SKILL.md` in place and click **Save**; the other files are kept. Frontmatter errors are marked in the editor, and **Save** stays disabled until they are fixed. To replace every file, click **Upload new version** and choose a new archive; the `SKILL.md` name must stay the same. ## List, inspect, delete Each skills page lists the skills owned at that scope; `omnara skills list` does the same from the terminal. Fetching a skill (`omnara skills get {skill-id}` or the API) returns `skill_md`, the full instruction text of its current revision. To delete an owned skill in the dashboard, open its row menu and click **Delete**, or run `omnara skills delete {skill-id}`. Deletion removes its revisions and grants, but is blocked with `409` while an active agent config references it. ## Grant it to projects A project can use a skill only if it owns it or holds a grant — org- and user-owned skills must be granted to each project that references them. Grants share without transferring ownership. > [tab] API > > The response identifies the grant and target project: > > ```bash CLI theme={null} > omnara grant skills add skl_ik7mv4qtrwz3jehcyd5n6a2bfg \ > --target-project-id proj_4qtrwz3jehcyd5n6a2bfgik7mv \ > --json > ``` > > ```bash REST theme={null} > curl "$OMNARA_API/orgs/$ORG/skills/skl_ik7mv4qtrwz3jehcyd5n6a2bfg/grants" \ > -H "Authorization: Bearer $OMNARA_TOKEN" \ > -H "Content-Type: application/json" \ > -d '{"target_project_id": "proj_4qtrwz3jehcyd5n6a2bfgik7mv"}' > ``` > > ```typescript SDK theme={null} > await sdk.createSkillGrant({ > client, > path: { orgID, skillID: 'skl_ik7mv4qtrwz3jehcyd5n6a2bfg' }, > body: { target_project_id: 'proj_4qtrwz3jehcyd5n6a2bfgik7mv' }, > }) > ``` > > ```json theme={null} > { > "id": "skg_jehcyd5n6a2bfgik7mv4qtrwz3", > "org_id": "org_k7mv4qtrwz3jehcyd5n6a2bfgi", > "skill_id": "skl_ik7mv4qtrwz3jehcyd5n6a2bfg", > "target_project_id": "proj_4qtrwz3jehcyd5n6a2bfgik7mv", > "created_at": "2026-08-03T20:34:52Z" > } > ``` > > List a skill's project grants with `omnara grant skills list {skill-id}` or `GET …/grants`; revoke one with `omnara grant skills delete {skill-id} {grant-id}` or `DELETE …/grants/{grantID}`. > > To confirm which skills a project can use (the CLI has no equivalent command): > > ```bash REST theme={null} > curl "$OMNARA_API/orgs/$ORG/projects/$PROJ/skills" -H "Authorization: Bearer $OMNARA_TOKEN" > ``` > > ```typescript SDK theme={null} > const available = await sdk.listProjectAvailableSkills({ > client, > path: { orgID, projectID }, > }) > ``` > > Each result reports whether the project owns the skill (`direct`) or receives it through a `grant`. > [tab] Dashboard > > On the skill's row, click **Share with project** and pick the target. Shared skills appear on the project's **Skills** page, **Shared** tab — **Stop sharing skill** revokes one. Anyone who can view the project can open a shared skill to read it; only its owner can edit it. ## Attach it to an agent Reference the skill ID in the config; config creation verifies the project can use it: ```yaml theme={null} skills: - skl_ik7mv4qtrwz3jehcyd5n6a2bfg ``` In the dashboard's agent builder, click **+** in the **Skills** section and search for a skill to attach it. The search list includes **Create skill**, first when nothing matches the search and after the matching skills otherwise. It opens the same upload dialog, creates the skills in the project, and attaches them. A created skill replaces an attached skill with the same name. Attaching a skill automatically enables the built-in `skill` tool. The model sees each skill's name and description; when it calls the tool, Omnara returns the `SKILL.md` instructions and attempts to install the supporting files on each attached machine. The instructions still work when no machine is attached or an installation fails. For a complete example you can deploy, which uploads Remotion's skill folder and attaches it to a video agent, see the [video generation agent](/examples/video-generation-agent). > [!NOTE] > > Full schema and playground: [Create skill](/api-reference/endpoints/skills/create-skill) · [Update skill](/api-reference/endpoints/skills/update-skill-by-uploading-a-new-revision) · [List skills](/api-reference/endpoints/skills/list-skills-visible-through-ownership-authority) · [Skill grants](/api-reference/endpoints/skills/create-skill-grant) · [Project skills](/api-reference/endpoints/skills/list-skills-available-to-project).