# Omnigent Full Documentation > Expanded LLM context for Omnigent. This file contains the homepage summary and cleaned Markdown content for every quickstart, documentation, blog, and release page listed in /llms.txt. Source: https://omnigent.ai Generated from: https://github.com/omnigent-ai/omnigent-site --- ## https://omnigent.ai/ # Omnigent Omnigent is a common layer over Claude Code, Codex, Pi, and custom agents. It lets teams swap or combine harnesses without rewriting agents, govern them with policies and secure sandboxes, and collaborate in real time on the same live session from terminal, web, desktop, mobile, or API interfaces. Core capabilities: - Built-in multi-AI agents, including Polly for coding orchestration and Debby for model debate. - Custom agents written in YAML with configurable harnesses, models, prompts, skills, tools, and policies. - Contextual policies for safety, spend caps, model routing, and risk-based escalation. - Secure sandboxes for any agent: local OS sandboxing on Linux and macOS for filesystem, network, environment, and credential boundaries, or remote cloud sandbox hosts. - Shared sessions, comments, forks, and multi-device collaboration. - Deployable shared server with database, authentication, and cloud sandbox host options. --- ## https://omnigent.ai/quickstart/coding-agent # Tutorial: Coding Agent Already using Claude Code or Codex? This tutorial shows you what Omnigent adds on top: a web UI with file editing and inline comments, mobile access, session sharing, and the ability to fork a session and continue it with a different agent. None of this is possible with a native coding agent alone. **Time:** 5 minutes. **Everything runs locally.** ## 1. Start your coding agent ``` omni claude # Claude Code omni codex # Codex ``` Pick whichever you already use. Either command launches the coding agent in your terminal and opens a web UI at `http://localhost:6767`. Both are connected to the same session and stream output in real time. ## 2. Give it a task Ask it to do something in your project. For example: ``` Write a Python function that reads a CSV file and returns the top 5 rows sorted by the second column. ``` Watch it work in the terminal. Then follow along in the web UI or the desktop app. Terminal, browser, and desktop app are three views of the same session: type in one, and the others update in real time. **Web UI** Open `http://localhost:6767` in your browser. The web UI shows your conversation on the left and a right panel with four tabs: **Files** (browse and edit files the agent created), **Agents** (sub-agents if any), **Shells** (running terminals), and **Todos**. Everything syncs with the terminal in real time. ![Omnigent web UI running a coding agent session](https://omnigent.ai/images/docs/single-harness-demo.gif) **Desktop App** Launch the [desktop app](https://omnigent.ai/docs/interact/desktop) and connect it to `http://localhost:6767`. You get the same session and the same panels as the web UI, plus OS notifications and a dock badge when the agent finishes or needs your input, which is handy when you switch away while it works. ![Omnigent desktop app showing an agent conversation alongside the session workspace](https://omnigent.ai/images/docs/desktop-conversation.png) ## 3. Try native Omnigent features Now that your agent has produced some output, try these features in the web UI: ### Comment on the agent's work Click on a specific part of the agent's output and leave an inline comment like "make this more concise" or "use pandas instead". The agent sees exactly what you're referring to and revises it. No need to describe the location in chat. ![An inline comment on agent output in the Open tab, with Open and Sent counts and the Address All button](https://omnigent.ai/images/docs/address-comment.png) ### Edit files directly Open any file the agent created in the built-in editor. Make changes yourself with syntax highlighting. The agent can see your edits and continue from there. ![Editing a file in the Omnigent web UI](https://omnigent.ai/images/docs/file-editor.gif) ### Paste an image Drag a screenshot, mockup, or diagram into the chat. The agent sees it alongside your text. Try pasting a screenshot of a UI bug or a design spec. ### Fork a session to switch agents Fork your current session to try a different approach without affecting the original. The fork copies the full conversation history up to that point, so you can experiment freely. Sessions in Omnigent belong to you, not to a specific agent, so a copy doesn't have to continue with the agent it started with. Forking from the CLI (`omni run --fork `) keeps the same agent. To switch, use **Clone session** in the web UI and pick the new agent. **Started in Claude Code? Clone the session and continue the copy with Codex (or vice versa).** The new agent picks up the full conversation history and keeps going, so you can compare how each one tackles the same problem from the same starting point. ## 4. Add a policy in plain language Type this into the chat: ``` Don't send any PII like emails or credit card numbers to the model. Ask me first if a message contains some. ``` The agent attaches Omnigent's built-in PII policy to the session. From now on, every outgoing model request is scanned, and anything containing PII is held for your approval before it leaves your machine. Try it: paste a log file with a fake email address and watch the policy step in. This is enforcement at the platform layer, something no coding agent can do on its own. Cost budgets, access controls, and more work the same way. See [Contextual Policies](https://omnigent.ai/docs/policies/overview). ## What's next - [Try Polly](https://omnigent.ai/quickstart/polly) to see multi-agent coding orchestration in action. - [Collaborate from anywhere](https://omnigent.ai/quickstart/collaborate) to deploy a server and share sessions with teammates. - [Build a custom agent](https://omnigent.ai/docs/use/custom-agents) with your own tools, prompts, and policies. --- ## https://omnigent.ai/quickstart/collaborate # Tutorial: Collaborate from Anywhere So far everything runs on your laptop. This tutorial takes you beyond that: deploy a server so you can access your agents from your phone, share live sessions with teammates, and run agents on cloud sandbox hosts that keep working after you close your laptop. **Time:** 15 minutes. **Requires a Railway account (free tier works).** ## 1. Deploy to Railway Railway is the quickest way to get a multi-user Omnigent server running. 1. Go to [railway.app](https://railway.app) and import the [Omnigent repo](https://github.com/omnigent-ai/omnigent) from GitHub, then add a Postgres plugin to the project. 2. Adding the plugin does not wire the database into the app on its own. Open the omnigent service's **Variables**, add a `DATABASE_URL` variable, and set it to a reference to the Postgres service: `${{Postgres.DATABASE_URL}}`. Until this is set, the deploy errors with `DATABASE_URL is required`; add it, then redeploy. 3. Once deployed, note your server URL (something like `https://omnigent-production-xxxx.up.railway.app`). ## 2. Connect your laptop ``` omni login https://your-server.up.railway.app omni host https://your-server.up.railway.app ``` `login` authenticates you. `host` registers your machine so the server can dispatch agent work to it. You're now running agents through the deployed server. ## 3. Access from your phone Open `https://your-server.up.railway.app` on your phone's browser (the same URL from step 1). You see the same web UI with the same sessions. Start a task on your laptop, check progress from your phone over lunch, answer an approval prompt from the coffee line. ## 4. Share a session with a teammate In the web UI, click **Share** on any session. Send the link to a teammate. They open it in their browser and join in real time. Now both of you can: - Watch the agent's output as it streams. - Send messages to the agent. - Comment on the agent's work. - Fork the session to try a different direction without affecting the original. **Only share Edit with people you trust to run arbitrary code on your machine.** A teammate with Edit can drive the agent, which runs code on whatever machine hosts the session. Share read-only when you can. See [Co-driving grants code execution on the host](https://omnigent.ai/docs/collaborate#code-execution-risk) for details. ## 5. Run a cloud sandbox host Want the agent to keep working after you close your laptop? Launch a cloud sandbox host from the web UI. The agent runs in a remote container and you check results whenever you're ready. Set it up by adding a `sandbox` section to your server config. See [Cloud Sandbox Host](https://omnigent.ai/docs/deploy/cloud-sandbox-host) for the full setup guide. ## What's next - [Set up SSO](https://omnigent.ai/docs/collaborate/auth) with Google, GitHub, or Okta for your team. - [Explore other deployment options](https://omnigent.ai/docs/deploy/overview): Docker, Render, Fly.io. - [Build a custom agent](https://omnigent.ai/docs/use/custom-agents) that your whole team can use. --- ## https://omnigent.ai/quickstart/install # Install Install the CLI, set up your credentials, and launch your first agent. Prefer a native window? Add the [desktop app](#install-the-desktop-app). ## Requirements - **Python 3.12+** - **Node.js 22 LTS** and npm - **tmux** ## Install Omnigent The base install covers everyday use. Extra integrations are opt-in at install time. Run the same command to upgrade an existing installation. **installer** ```sh curl -fsSL https://omnigent.ai/install.sh | sh # install an optional integration curl -fsSL https://omnigent.ai/install.sh | sh -s -- --extra databricks,modal ``` **uv** ```sh uv tool install omnigent # install an optional integration uv tool install "omnigent[databricks,modal]" ``` **pip** ```sh pip install omnigent # install an optional integration pip install "omnigent[databricks,modal]" ``` **Homebrew** ```sh brew install omnigent-ai/tap/omnigent ``` Available extras: | Category | Extras | | ------------------ | ---------------------------------------------------------------------------------------------------- | | Model providers | `databricks`, `bedrock`, `vertex` | | Sandbox providers | `databricks`, `modal`, `daytona`, `blaxel`, `boxlite`, `cwsandbox`, `e2b`, `openshell`, `kubernetes` | | SDK harnesses | `antigravity`, `copilot`, `cursor`, `agents-sdk` | | Storage and memory | `postgres`, `s3`, `hindsight` | ## Homebrew source builds behind a mirror The Homebrew tap installs a prebuilt bottle by default. If you build from source on a network that requires internal package mirrors, point the build at them with two optional environment variables: ```bash HOMEBREW_PIP_INDEX_URL='https://pypi.example.com/simple' \ HOMEBREW_CARGO_INDEX_URL='https://cargo.example.com/index/' \ brew install --build-from-source omnigent-ai/tap/omnigent ``` - `HOMEBREW_PIP_INDEX_URL` routes pip through your mirror, including pip's isolated build dependencies. - `HOMEBREW_CARGO_INDEX_URL` takes a sparse registry index URL ending in `/`, without the `sparse+` prefix. Both overrides are optional and do not affect prebuilt-bottle installs. Replace the example URLs with your own mirrors. ## Set up credentials The wizard detects credentials in your environment and prompts for any that are missing. See [Models & Credentials](https://omnigent.ai/docs/build/models#credentials) for the full reference. ``` omni setup ``` ## Launch Debby Try your install with Debby, a built-in multi-AI agent that sends every question to both Claude and GPT and lets them debate. ``` omni debby ``` This launches a web UI and prints the server URL and port. Ask something like `What came first, the chicken or the egg?` and watch the models challenge each other. See the [Debby reference](https://omnigent.ai/docs/use/builtin-agents/debby) for more. ## Install the desktop app The desktop app gives you the full UI in a native window, and adds OS features a browser can't, like notifications and a dock badge. [Download for Apple silicon](https://omnigent.ai/download/mac) · [Download for Intel](https://omnigent.ai/download/mac-x64) ![Omnigent desktop app first-launch setup screen](https://omnigent.ai/images/docs/desktop-start.png) 1. **Launch the app.** On first launch, you'll see a setup screen. 2. **Enter your server URL.** `http://localhost:6767` if running locally, or your deployed server URL (e.g. `https://your-app.onrender.com`). 3. **Start working.** The app connects and shows the same UI you'd see in a browser. See the [Desktop app](https://omnigent.ai/docs/interact/desktop) reference for all platforms and details. ## Upgrade the CLI Self-upgrade an existing install to the latest release with: ``` omni upgrade ``` Add `--check` to report whether a newer version is available and exit without changing anything. `omni update` is an alias for `omni upgrade`: it runs the same command with the same options, so either spelling works. The alias is not listed in `omnigent --help` to avoid a duplicate entry. ## Nightly builds Nightly builds are datestamped prereleases (`vX.Y.Z.devYYYYMMDD`) cut from the newest green commit on `main` around 04:30 UTC each day. They are git tags that never reach the package index, so you install them straight from GitHub. Use them to try the latest changes ahead of a tagged release. Building a nightly compiles the web UI, so it needs **git**, plus **Node.js 22** and **pnpm**, in addition to the base requirements. To move an existing install onto the newest nightly: ``` omni upgrade --nightly ``` This resolves the newest nightly tag and reinstalls Omnigent pinned to it, using whichever installer you originally used. It works from any install shape: a package-index install hops onto the nightly channel, an older nightly moves forward, and a rerun on the current nightly is a no-op. The following flags compose with `--nightly`: - `--check` — report the newest nightly and exit non-zero if upgrading would change the install, without changing anything. - `--dry-run` — print the command that would run and exit. - `--extra` — keep or add extras (comma-separate to add several). `--pre` and `--target-version` do **not** apply to `--nightly` — it always tracks the newest nightly tag. To install one specific nightly, run your installer against that tag directly: ``` uv tool install --force git+https://github.com/omnigent-ai/omnigent@v0.8.0.dev20260801 ``` Because `pip` and `uv pip` do not record which extras were requested, a package-index install made with either cannot preserve extras through `omni upgrade --nightly`. In that case Omnigent prints the manual git command to run instead. To install or update the nightly on a schedule (for example, from cron), use the `update_nightly.sh` helper. It resolves the newest nightly tag, installs it with `uv`, and exits fast when you are already current: ``` 0 9 * * * bash /path/to/update_nightly.sh >> "$HOME/.omnigent-nightly.log" 2>&1 ``` ## Uninstall `omni uninstall` removes Omnigent while preserving your data by default. With no destructive flag, it runs a dry-run preview and prints what it would do without changing anything: ``` omni uninstall ``` To actually remove the CLI and the installer-managed PATH entries, add `--yes`. Your local history, credentials, and projects stay in place: ``` omni uninstall --yes ``` To also delete Omnigent state under `~/.omnigent`, add `--purge`. Omnigent archives that state to a backup outside the target (under `~/.omnigent-backups/`) and prints a restore command before deleting; pass `--no-backup` to skip the archive. Your `~/omnigent` workspace is kept unless you also pass `--purge-workspace`: ``` omni uninstall --purge --yes ``` You can scope the run to one or more targets: `cli`, `state`, `desktop-data`, or `all`. Other flags cover less common cases: `--dry-run` to force a preview, `--json` for machine-readable output, `--force` to stop stubborn processes and override tamper refusals, and `--modify-external-config` to allow marker-scoped edits to third-party config files. If `omni` itself won't run (broken wheel, or not on your `PATH`), use the standalone uninstall script instead. It takes the same flags, so add `--yes` to perform the previewed cleanup: ``` curl -fsSL https://raw.githubusercontent.com/omnigent-ai/omnigent/main/scripts/uninstall_oss.sh | sh ``` --- ## https://omnigent.ai/quickstart/policies # Tutorial: Contextual Policies Omnigent ships with a set of context-aware policies you can attach to any live session, no code required. They watch what your agent is doing and step in when it matters: warning you about spend, pausing for approval, or blocking a risky action outright. This tutorial walks you through setting one up on a running session. **Time:** 5 minutes. **Everything runs locally.** ## 1. Start a new session Open the web UI and start a new session with whichever agent you like. We'll use a Claude Code session for this walkthrough, but policies work the same across every agent type. ![Starting a new Claude Code session in the Omnigent web UI](https://omnigent.ai/images/docs/policy-new-session.png) ## 2. Add a policy to the session Click the info button `(i)` at the top of the page to open the session panel. You'll see the current session cost and a **Policies** section. Hit the `+` button, then pick a policy from the list. There are plenty to choose from; for this tutorial we'll use **Session Cost Budget**, which keeps an eye on how much the session is spending on the model. The clip below walks through the whole flow: opening the panel, choosing the policy, and filling in its settings. ![Opening the session info panel, choosing Session Cost Budget, and configuring its limits](https://omnigent.ai/images/docs/policy-add-policy.gif) As you can see in the clip, the cost policy gives you two kinds of guardrails: - **Soft limits**: a list of spend thresholds that act as gentle warnings. When the session cost crosses one, you get a heads-up on the next tool call and can decide whether to keep going. For the demo we'll set a soft limit of `0.01`. - **Hard limit**: a ceiling paired with a list of **expensive models**. Once spend passes the hard limit, the session blocks any of those pricey models until you downgrade to a cheaper one. We'll set the hard limit to `0.2` and keep the default expensive-model list (Opus, GPT-5.5). Once you fill in the values and click **Add**, the policy goes live on the session right away: you'll see it listed under **Policies** in the panel. ## 3. Watch the policy in action Keep an eye on the running cost in the info panel `(i)` as you chat. Now let's trip each guardrail. ### Cross the soft limit Once spend passes your soft limit, ask the agent to run any tool. Instead of charging ahead, it pauses and asks whether you want to continue. Approve to keep going, or reject to stop the call. ![Approval prompt shown after the session cost crosses the soft limit](https://omnigent.ai/images/docs/policy-soft-limit-approval.gif) ### Hit the hard limit When spend reaches the hard limit, the policy blocks every following tool call that would use an expensive model. The agent tells you what happened and waits. Switch to a cheaper model and you're free to continue. ![Expensive models blocked after the session hits the hard cost limit, then unblocked by downgrading](https://omnigent.ai/images/docs/policy-hard-limit-blocked.gif) That's the whole loop: warn, ask, block. The same pattern applies to every built-in policy, so you can mix and match them to fit how you want a session to behave. ## What's next - [Contextual Policies overview](https://omnigent.ai/docs/policies/overview): all the ways to apply policies (chat, Omnigent YAML, or server config). - [Builtin Policies](https://omnigent.ai/docs/policies/builtin): the full catalog of policies you can add out of the box. - [Custom Policies](https://omnigent.ai/docs/policies/custom): write your own in Python when the builtins don't cover your use case. --- ## https://omnigent.ai/quickstart/polly # Tutorial: Polly (Multi-AI Coding) Polly is a multi-agent coding orchestrator. It breaks your task into sub-tasks and delegates each one to a different AI agent, with cross-vendor code review built in. This tutorial shows you how it looks in practice. **Time:** 5 minutes. **Everything runs locally.** ## 1. Run Polly ``` omni polly ``` This starts Polly and opens a web UI at `http://localhost:6767`. Polly is also the default agent `omni run` launches when a Claude credential is configured. (A bare `omni` with no arguments starts the local server / host in the background instead — use `omni polly` or `omni run` to launch an agent.) ![Running omni polly from the terminal launches Polly](https://omnigent.ai/images/docs/polly-default-launch.gif) Start Polly with `omni polly`, or `omni run` when a Claude credential is configured. ## 2. Give it a task ``` Refactor the authentication module into separate files for OAuth, JWT, and session handling. Add tests for each. ``` Polly breaks this into sub-tasks and delegates each one to a different AI agent. Claude Code implements one piece, Codex implements another in parallel, and a different agent reviews each PR. ## 3. Watch it in the web UI or desktop app **Web UI** Open `http://localhost:6767` in your browser. It shows the same session as your terminal, updating in real time. **Desktop App** Launch the [desktop app](https://omnigent.ai/docs/interact/desktop) and connect it to `http://localhost:6767`. It shows the same session as your terminal, and adds OS notifications and a dock badge when Polly or a sub-agent needs your attention, which is useful while sub-agents work in parallel. The right panel has four tabs: **Files**, **Agents**, **Shells**, and **Todos**. Click the **Agents** tab to see every sub-agent Polly dispatches, with live status (working, idle, finished). - Click any sub-agent to switch to its view and see its conversation, files, and terminal output. - Switch to the **Shells** tab to see all running shells side by side. - Intervene at any point: send a message to a sub-agent, or switch back to Polly to see the orchestrator's view. ![Switching from Polly to sub-agents in the Agents panel](https://omnigent.ai/images/docs/polly-agent-switch.gif) The Agents panel shows Polly and its sub-agents, so you can inspect or steer each delegated worker directly. This is where multi-AI orchestration becomes tangible: you can watch Claude Code and Codex working on different parts of the same task simultaneously, each in its own git worktree. See [Polly reference](https://omnigent.ai/docs/use/builtin-agents/polly) for the full details on skills like `/fanout`, `/cross-review`, and `/investigate`. ## What's next - [Collaborate from anywhere](https://omnigent.ai/quickstart/collaborate) to deploy a server and share sessions with teammates. - [Build a custom agent](https://omnigent.ai/docs/use/custom-agents) with your own multi-agent orchestration in YAML. --- ## https://omnigent.ai/docs/use/builtin-agents # Built-in Multi-AI Agents Omnigent ships with multi-agent orchestrators that combine different AI agents in a single workflow. Both are ready to run out of the box, with more coming. ## [Polly](https://omnigent.ai/docs/use/builtin-agents/polly) Multi-agent coding orchestrator. Breaks tasks into sub-tasks, delegates to Claude Code, Codex, OpenCode, Cursor, Hermes, Antigravity, and Pi sub-agents in separate git worktrees, and enforces cross-vendor review. Run `omni polly` — the same default agent `omni run` launches when a Claude credential is configured. ## [Debby](https://omnigent.ai/docs/use/builtin-agents/debby) Multi-model brainstorming partner. Sends every question to both Claude and GPT, with a `/debate` skill for multi-round critique. Run `omni debby`. --- Polly and Debby are just YAML configs. You can build your own multi-agent orchestrator the same way. See [Custom Agents](https://omnigent.ai/docs/use/custom-agents) to learn how. --- ## https://omnigent.ai/docs/use/builtin-agents/debby # Debby Multi-model brainstorming partner. Debby sends every question to both Claude and GPT simultaneously, then lets them debate and refine each other's answers. ``` omni debby ``` ## How it works Ask a question and both models respond independently. You see both perspectives side by side. Then use the `/debate` skill to start a multi-round critique where each model reviews the other's answer, challenges weak points, and refines its own response. | Sub-agent | Role | | --------- | --------------------------------------------------- | | `claude` | Responds and debates with an Anthropic Claude model | | `gpt` | Responds and debates with an OpenAI GPT model | ## The /debate skill Type `/debate` after getting initial answers to trigger multi-round critique. Each model reviews the other's answer, points out weaknesses, and improves its own. The debate converges toward a stronger synthesis than either model would produce alone. ## When to use - Architectural decisions where you want genuinely independent perspectives. - Comparing approaches before committing to one. - Stress-testing an idea by having two models challenge each other. - Catching blind spots a single model might miss. [Source on GitHub](https://github.com/omnigent-ai/omnigent/tree/main/examples/debby) --- ## https://omnigent.ai/docs/use/builtin-agents/polly # Polly Multi-agent coding orchestrator. Polly breaks your task into sub-tasks and delegates each one to a different AI agent, with cross-vendor code review built in. ``` omni polly omni run # launches Polly, the default agent, when a Claude credential is configured ``` ## How it works Polly is a supervisor that never writes code itself. It decomposes your goal into sub-tasks and delegates each one to a sub-agent running on its own harness and git worktree. One agent implements, a different agent reviews. Each implementer opens its own PR. Polly never merges. The human decides. | Sub-agent | Harness | Role | | ------------- | -------------------- | -------------------------------- | | `claude_code` | `claude-native` | Claude Code | | `codex` | `codex-native` | Codex | | `opencode` | `opencode-native` | OpenCode | | `cursor` | `cursor-native` | Cursor | | `hermes` | `hermes-native` | Hermes Agent (Nous Research) | | `agy` | `antigravity-native` | Google Antigravity CLI (Gemini) | | `pi` | `pi` | Pi (headless, any gateway model) | `claude_code`, `codex`, `opencode`, `cursor`, `hermes`, and `agy` are the primary implementers, each running as a real CLI coding harness in its own terminal you can open in the UI's Subagents panel to watch or take over. `pi` runs headless as the review/explore specialist. Seven distinct vendors means any implementer's diff can be reviewed by a different one — the cross-vendor check is the point. `agy` is Gemini-native: it authenticates through its own Google account (via `~/.gemini`) or a Gemini credential configured in `omni setup` (a Google API key, native Gemini gateway, or Databricks profile — see [Antigravity configuration](https://omnigent.ai/docs/build/harnesses/configuration#antigravity)), and does not run Claude- or GPT-family models, which makes it a natural third vendor for cross-review. Each worker needs its CLI on `PATH` — `agy` requires the `agy` binary, joining `claude`, `codex`, `opencode`, `cursor-agent`, `hermes`, and `pi` in Polly's roster preflight. ## Tool approvals in headless workers Polly's native workers launch in a headless pane where no human is watching to answer a tool-approval prompt, so an `Edit`/`Write`/`Bash` that would prompt stalls the worker. Each worker's bundle therefore declares a full-bypass stance so it can edit and run unattended: | Worker | Harness | Bypass config | Effect | | ------------- | --------------- | ----------------------- | ------------------------------------------------- | | `claude_code` | `claude-native` | `permission_mode: auto` | passed through as `--permission-mode auto` | | `codex` | `codex-native` | `yolo: true` | `--dangerously-bypass-approvals-and-sandbox` | | `cursor` | `cursor-native` | `yolo: true` | `--yolo` (cursor-agent's don't-ask / full bypass) | Set `executor.config.yolo: false` on a worker to keep it prompting — useful for a read-only or must-approve sub-agent (its gated calls then surface as approval cards). For the Cursor worker, `executor.config.permission_mode: auto` (or `exec_mode: auto` / `auto-review`) selects Smart Auto (`--auto-review`) instead of full `--yolo`, matching Claude's `permission_mode: auto` ergonomics. Bypass only skips the interactive per-tool prompt — Omnigent's `blast_radius` policy still `DENY`s the catastrophic set. ## Skills - **`/fanout`** runs independent tasks in parallel. Each gets its own git worktree and sub-agent. Each opens its own PR. - **`/cross-review`** sends an implementer's diff to a different-vendor reviewer. Blocking issues loop back as fixes until clean. - **`/investigate`** delegates read-only work (debugging, audits, code understanding) to sub-agents and synthesizes findings. ## Goal mode In the web UI, for a top-level Polly session, the composer shows a **Goal** button next to the input. Click **Goal**, enter a **Completion condition** (for example, `All tests pass and the implementation is complete`), and choose **Start goal**. Polly keeps working until that condition is met, with progress and completion reported in the conversation. Omnigent sends this to Polly as a `/goal ` command — posted immediately if the session is idle, or queued to open the next turn if one is already streaming — so the objective persists across turns rather than being a one-off instruction. Goal mode is available on top-level Polly sessions whose **brain harness** is `claude-sdk` or `codex`. This is the harness Polly's own supervisor loop runs on — not the sub-agent harnesses in the table above, and sub-agent sessions never show the control (including those on `claude-native` and `codex-native`). `omni polly` runs the Claude variant; to use the Codex brain harness, start a new Polly session from the web UI and pick the Codex harness. - To stop early on the Claude harness, send `/goal clear`. Codex has no equivalent: sending `/goal clear` on Codex sets a goal whose objective is the literal string `clear`, so don't use it to stop. - A goal can also end without succeeding: Codex reports `blocked`, `usageLimited`, or `budgetLimited`, and Claude can report the condition as unachievable. - In read-only (shared, view-only) sessions the button is disabled. ## When to use - Large refactors that benefit from parallel work. - Cross-vendor code review where one agent writes and another reviews. - Specialized sub-tasks routed to the harness best suited for them. [Source on GitHub](https://github.com/omnigent-ai/omnigent/tree/main/examples/polly) --- ## https://omnigent.ai/docs/use/coding-agents # Coding Agents Run Claude Code or Codex through Omnigent with a single command. No YAML needed. You get the full platform on top of the coding agent you already use: web UI, persistent sessions, team collaboration, mobile access, and [contextual policies](https://omnigent.ai/docs/policies/overview). ``` omni claude # Claude Code omni codex # Codex omni goose # Goose omni hermes # Hermes omni copilot # GitHub Copilot (SDK harness) omni devin # Devin ``` Each command launches a built-in agent — no registration step required. Goose, Hermes, and Devin boot their own native terminal UI in a pane, wrapped with Omnigent's web UI, collaboration, and policy layer. `omni copilot` is shorthand for `omni run --harness copilot` and needs the Copilot SDK (`copilot` extra); see [GitHub Copilot](https://omnigent.ai/docs/build/harnesses/configuration#github-copilot) for launch and auth details. See [Harnesses](https://omnigent.ai/docs/build/harnesses) for the full list of coding agents and their harness ids. Under the hood, each coding agent runs as a **harness**: the runtime that executes your agent loop. Omnigent wraps it with a common interface and adds everything a single harness can't provide on its own. **[Follow the Coding Agent tutorial](https://omnigent.ai/quickstart/coding-agent)** to get started in under a minute. ## Import existing chats Already have local Claude Code or Codex history? Bring it into Omnigent as a normal session with `omnigent import`. The source transcript is converted to ordinary Omnigent items and stored as a regular session, so it gains the web UI, persistence, collaboration, and everything else. A given source session can only be imported once unless you pass `--force` to replace the previous import. Import a single chat by its harness-native session ID: ``` omnigent import --harness claude --session omnigent import --harness codex --session ``` Or import a batch of your most recent chats with `--last N`, which imports the `N` most recently modified parent sessions (oldest first, up to a maximum of 100): ``` omnigent import --harness claude --last 10 ``` Provide exactly one of `--session` or `--last` — they are mutually exclusive. To pull recent chats from every supported harness at once, pass `--harness all` together with `--last N`. This merges all harnesses into a single global recency order and imports the `N` most recent sessions in total (not `N` per harness), each tagged with its own source: ``` omnigent import --harness all --last 25 ``` `--harness all` requires `--last` — a single `--session` ID belongs to exactly one harness, so combining `all` with `--session` is a usage error. Re-importing a source session that was already imported is rejected by default. Pass `--force` to replace the previous import instead — Omnigent deletes the old conversation and re-imports the transcript in place, keeping the same session ID: ``` omnigent import --harness claude --session --force ``` When importing a batch, sessions that were already imported are skipped and a session that fails to import doesn't stop the rest. Omnigent prints a summary of how many sessions were imported, already imported, and failed, and exits with a non-zero status if any failed. ### Import from the web UI You can also import from the web UI without touching the terminal. Open **Settings › Import sessions** and pick one of your online machines. The **Import** selector offers two modes: - **Recent sessions** — choose a harness (or **All harnesses**) and how many recent sessions to pull (25, 50, or 100). - **Session by ID** — choose a specific harness and enter one harness-native session ID to import just that transcript, without listing your local history. **All harnesses** isn't available in this mode, since a session ID belongs to exactly one harness. Then click **Import**. The transcripts live on your machine, so the read happens on that connected machine over its tunnel — the server never sees your local files. Sessions already imported are skipped, and each newly imported session is linked in the result so you can jump straight into it. A brand-new account with no sessions yet also gets a one-click **Import your 25 most recent sessions** button on the empty home screen (importing across all harnesses from your online machine), plus a **Choose what to import** link into the Settings picker. Importing from the web UI requires an online machine — start one with `omnigent host` from your terminal if none are connected. --- ## https://omnigent.ai/docs/use/custom-agents # Custom Agents A custom agent is defined in a short YAML file. No imperative code, no framework subclassing. The YAML file **is** the agent. You get the entire Omnigent platform for free: web UI, persistent sessions, team collaboration, deployment infrastructure, and [contextual policies](https://omnigent.ai/docs/policies/overview). ## Create it with your coding agent The recommended way to create a custom agent is to ask your coding agent to build it for you in natural language: ``` You: Build me a documentation reviewer agent. It should use Claude as the model, have access to the file system and GitHub, and follow our style guide at docs/STYLE.md. Agent: Created "docs-reviewer" agent. Opening a new session with it now. You can select it from the session dropdown anytime. ``` The agent writes the YAML behind the scenes and registers it so you can reuse it. If you want to tweak the config later, the YAML file is there to edit directly. For an agent you've installed on a server, you can also change its description, instructions, model, harness, or MCP servers without re-uploading the bundle; see [Edit an installed agent's configuration](https://omnigent.ai/docs/programmatic#edit-an-installed-agents-configuration). to edit directly, or you can [edit it from the web UI](#edit-an-agent-in-the-web-ui). ## Edit an agent in the web UI Agents you own can be edited in place from **Settings › Custom agents** (`/settings/custom-agents/`) without reinstalling a bundle. Open an agent's summary and choose **Edit**. The **Edit** button appears only for agents you own, and only when the server advertises configuration editing (`agent_config_edit: true` in `/v1/info`); on an older server the summary says to update the server instead. Server-provided agents are read-only. The Settings page itself is a gated release feature: an operator enables it deployment-wide by including `custom_agents_settings_ui` in the server's release features (`OMNIGENT_FEATURES=custom_agents_settings_ui`). The **Edit** dialog loads the agent's current configuration and lets you change: - **Description**, **Harness**, and **Model**. The agent's name stays the same. Model and harness compatibility is checked by the server when you save, so an incompatible pair is reported as a save error rather than in the form. - **Instructions** — the agent's literal instruction text. - **MCP servers** — add an HTTP or stdio server (**Add server**), change a server's description, URL, or command and arguments, or mark a server for removal (**Remove server** / **Undo removal**). An existing server's name and transport can't be changed, and managed servers are shown read-only. Credentials (HTTP **Headers** or stdio **Environment variables**) are never read back into the editor — only their names are shown. Each configured value defaults to **Configured · keep**; switch it to **Replace** to enter a new value or **Remove** to delete it. Changing a server's URL or command leaves its configured credentials untouched. Saving sends only the fields you changed. **Save changes** stays disabled until something differs from the loaded configuration, and changes apply to sessions using the agent on their next turn. If the agent was changed elsewhere (for example in another tab) before you save, the dialog keeps your draft and asks you to **Reload latest**. Reloading, **Cancel**, Escape, and closing the dialog all ask for confirmation while you have unsaved changes (**Keep editing** / **Discard changes**). A browser refresh warns about an unsaved draft, but navigating with browser history can discard it. ## The config file Every custom agent lives in its own directory with a `config.yaml` at the root. You can place the directory anywhere on disk. Run it with: ``` omni run ./my-agent/ # directory containing config.yaml ``` ## What you can configure The config file supports these sections: ### [Harnesses](https://omnigent.ai/docs/build/harnesses) The harness is the runtime that executes your agent loop. Swap one line to switch runtimes. ``` executor: type: omnigent config: harness: claude ``` ### [Models & Credentials](https://omnigent.ai/docs/build/models) Pick the LLM that powers your harness. ``` executor: type: omnigent config: harness: claude model: claude-sonnet-4-6 ``` ### [Prompts & Skills](https://omnigent.ai/docs/build/prompts) Set the system prompt inline, or point at a file. ``` prompt: You are a concise coding assistant. # Or from a file: instructions: AGENTS.md ``` ### [MCP & Tools](https://omnigent.ai/docs/build/tools) Wire in MCP servers, Python functions, or sub-agents. ``` tools: github: type: mcp command: uv args: [run, python, -m, github_mcp] summarize: type: function callable: my_package.tools.summarize_file ``` ### [Policies](https://omnigent.ai/docs/policies/overview) Declarative guardrails in YAML. ``` policies: rate_limit: type: function handler: omnigent.policies.builtins.safety.max_tool_calls_per_session factory_params: limit: 50 ``` ## Putting it all together ``` spec_version: 1 name: coding_agent prompt: | You are a coding agent. Inspect files before editing, run targeted tests, and summarize changes with validation results. executor: type: omnigent config: harness: claude model: claude-sonnet-4-6 os_env: type: caller_process cwd: . sandbox: write_paths: [.] allow_network: true tools: repo_search: type: function callable: my_package.tools.repo_search policies: rate_limit: type: function handler: omnigent.policies.builtins.safety.max_tool_calls_per_session factory_params: limit: 50 ``` --- ## https://omnigent.ai/docs/build/harnesses # Harnesses A harness is the runtime that executes your agent loop. You can start a native harness session directly from the CLI without writing agent YAML: ``` omni claude omni codex omni pi ``` See [Terminal](https://omnigent.ai/docs/interact/terminal) for more launch commands. To select a harness in agent YAML, swap one line: ``` executor: harness: claude-sdk ``` Tools, policies, prompts, and models stay the same across harnesses — only the runtime underneath changes. ## Next steps - [Supported Harnesses](https://omnigent.ai/docs/build/harnesses/supported) lists every available harness, its ids and aliases, execution modes, and support status. - [Configuration](https://omnigent.ai/docs/build/harnesses/configuration) covers vendor setup, custom ACP agents, runtime selection, and launch overrides. - [Community Harnesses](https://omnigent.ai/docs/build/harnesses/community) explains how to package and register a new harness as a plugin. --- ## https://omnigent.ai/docs/build/harnesses/community # Community Harnesses The built-in harnesses ship with `pip install omnigent`. Beyond those, Omnigent discovers additional harnesses at startup through a plugin registry, so the community can add support for a new runtime as a **separate package** — without changing the core `omnigent` package. See [Supported Harnesses](https://omnigent.ai/docs/build/harnesses/supported) for details on how each integration is maintained and how its support can change. ``` pip install omnigent # core harnesses only pip install omnigent-foo # adds the `foo` harness to the same omni CLI ``` An installed plugin's harness shows up everywhere a built-in one does: it's accepted in agent YAML, honored by `--harness`, and merged into the harness picker in the web UI. Once your harness is published, we welcome pull requests that add its repository to the [Harnesses table](https://omnigent.ai/docs/build/harnesses/supported#harnesses). ## Build a plugin A harness plugin is a Python package that declares an entry point in the `omnigent.community.harness` group and exports a `get_contribution()` function. Implementation modules live under the `omnigent.community.harness.*` namespace. ```toml # pyproject.toml [project] name = "omnigent-foo" dependencies = ["omnigent"] [project.entry-points."omnigent.community.harness"] foo = "omnigent.community.harness.foo.plugin:get_contribution" ``` ```python # omnigent/community/harness/foo/plugin.py from omnigent.harness_plugins import HarnessContribution from omnigent.harness_install_spec import HarnessInstallSpec def get_contribution() -> HarnessContribution: return HarnessContribution( name="omnigent-foo", valid_harnesses=frozenset({"foo"}), harness_modules={ "foo": "omnigent.community.harness.foo.harness", }, aliases={"foo-code": "foo"}, harness_labels={"foo": "Foo"}, ) ``` Each harness module exports `create_app() -> FastAPI`; the runner imports it to launch the harness. Keep top-level imports in `plugin.py` light — entry-point discovery runs early, so put heavy imports inside the callables that need them. **Note:** Core rejects plugins that register flat package paths or try to override a built-in harness name. Community native-TUI harnesses aren't pluggable yet — the plugin interface covers direct and headless harnesses. See the [harness plugin interface](https://github.com/omnigent-ai/omnigent/blob/main/designs/harness-plugin-interface.md) design doc for the full plugin contract, including install/auth metadata, model-override env vars, and per-spawn environment builders. --- ## https://omnigent.ai/docs/build/harnesses/configuration # Configuration Choose a harness id from [Supported Harnesses](https://omnigent.ai/docs/build/harnesses/supported), then configure any authentication or launch settings it needs. ## GitHub Copilot The `copilot` harness runs GitHub Copilot through the Copilot SDK (install the `copilot` extra). Launch it with the `omni copilot` shorthand, or spell out the harness on `omni run`: ``` omni copilot # shorthand for the line below omni run --harness copilot ``` `omni copilot` forwards every `omni run` option unchanged, so flags like `-p "review the last commit"` or `--model ` work as they do on `omni run`. Because the shorthand always uses the Copilot harness, passing `--harness` to it is a usage error — use `omni run --harness ` to pick a different harness. Resume hints for a session started this way print the canonical `omni run --harness copilot` form. `omni --help` lists `copilot` under **Harnesses** only when the Copilot SDK is installed, the same way the `cursor` row follows its SDK extra. ### Authentication The `copilot` harness authenticates with a GitHub token that carries Copilot access. Omnigent resolves one from the first source that's set, in this order: 1. A `COPILOT_GITHUB_TOKEN`, `GH_TOKEN`, or `GITHUB_TOKEN` environment variable. 2. A token you stored through `omnigent setup` → *configure harnesses* → **Copilot**. 3. Your GitHub CLI login — if you've run `gh auth login`, Omnigent asks `gh` for the token directly (`gh auth token`), so a logged-in user is ready without pasting anything into setup. **Note:** The `gh` fallback runs `gh auth token` because the Copilot CLI only picks up a `gh` login by reading `oauth_token` from `~/.config/gh/hosts.yml` — which is absent whenever `gh` keeps the token in an OS keychain (the default on macOS). Asking `gh` itself works on every platform. ### GitHub Enterprise host Organizations that reach Copilot through a GitHub Enterprise (data-residency) instance need auth and API calls pointed at their own hostname rather than `github.com`. Set it through `omnigent setup` → *configure harnesses* → **Copilot** → **Set GitHub Enterprise host** (the same menu lets you change or clear it), or edit `~/.omnigent/config.yaml` directly: ```yaml copilot: github_host: acme.ghe.com ``` Provide a bare hostname; a pasted `https://` scheme or trailing slash is stripped automatically. When set, Omnigent forwards it to the bundled Copilot CLI as the `COPILOT_GH_HOST` environment variable and fetches your `gh` token for that host. With no host configured, Copilot uses its default `github.com`. ## Grok Build Grok Build (xAI's `grok` CLI) ships as a builtin harness with the id `grok` (alias `grok-build`). Under the hood it drives `grok agent stdio` over the [Agent Client Protocol](https://agentclientprotocol.com) — Omnigent renders its streaming output, reasoning, and tool cards and routes its permission requests through your [contextual policies](https://omnigent.ai/docs/policies/overview), just like the generic `acp` harness below. The difference is that it's built in: you select the `grok` harness id directly instead of registering a launch command. ``` omni run --harness grok ``` Like the generic ACP agents, Grok Build brings **its own auth and model** — Omnigent stores no credential and a `/model` pick is rejected up front (rather than silently dropped), so it always runs its account-default model. To pin one, configure an [`acp:` agent](#custom-acp-agents) whose command passes the `grok` CLI's own model flag. Install the CLI and log in through xAI first: ``` curl -fsSL https://x.ai/cli/install.sh | bash grok login --device-auth # xAI OAuth (device-code capable) ``` Use the vendor login rather than an API key. The builtin `grok` row has no `env_passthrough` of its own, and `XAI_API_KEY` is not in the host-to-runner credential allowlist, so exporting it in your shell does **not** reach the agent. If you need the key route, pass it explicitly with `OMNIGENT_RUNNER_ENV_PASSTHROUGH=XAI_API_KEY`, or configure an [`acp:` agent](#custom-acp-agents) that declares the passthrough. ## Devin Devin (Cognition) ships as a built-in **native TUI** harness with the id `devin-native`. `omni devin` boots the resident Devin CLI in a runner-owned pane and mirrors it into an Omnigent conversation, so you get [contextual policies](https://omnigent.ai/docs/policies/overview), approval cards, resume, cost tracking, and Devin's sub-agents as child sessions on top of the native experience. ``` curl -fsSL https://cli.devin.ai/install.sh | bash devin auth login omni devin ``` Devin brings **its own auth** — Omnigent stores no credential. The CLI writes its own credential file and reads it back at launch, and `devin auth status` is a live probe, so `omni setup` reports the real sign-in state. Approvals surface two ways: Omnigent policies gate each request, tool call, and tool result, and Devin's own consent prompt is republished as a Chat approval card (answerable in the web UI or the embedded terminal). ### Model and effort Devin encodes reasoning effort as a suffix on the model id. Omnigent keeps model and effort as separate axes and recombines them at launch. Pass a model family slug or alias from `devin models list`, and optionally an effort rung: ``` omni devin --model opus --effort xhigh omni devin --model gemini --effort max ``` `--effort` accepts `low`, `medium`, `high`, `xhigh`, or `max`; a family with no matching rung falls back to its default variant, so a mismatched pair costs effort, not the session. You can also switch models mid-session with `/model`. ### Launch options - `--permission-mode` — one of `normal` (default), `accept-edits`, `smart`, `dangerous`, or `bypass`. - `--sandbox` — enable Devin's OS-level process sandbox for its `exec` tool. - `-p`, `--prompt` — send an initial chat input when the TUI starts. - `--resume [CONVERSATION]` — resume a prior Omnigent conversation; with no value it opens a picker scoped to `devin-native` sessions. Devin's own `--resume`/`-c`/`--config`/`--export` flags are reserved for Omnigent, which owns resume and hook wiring; use `omni devin --resume` instead. The `devin` and `native-devin` spellings both resolve to `devin-native`. The former built-in ACP row `devin-acp` was **removed in 0.14** and its id now aliases onto the native wrap, so existing sessions and scripts keep resolving. A user-configured [`acp:` agent](#custom-acp-agents) for Devin is unaffected. ## Jcode Jcode ([jcode.sh](https://jcode.sh)) ships as a builtin harness with the id `jcode`. Like Grok Build, it drives its vendor CLI (`jcode acp`) over the [Agent Client Protocol](https://agentclientprotocol.com) over stdio — Omnigent renders its streaming output, reasoning, and tool cards and routes its permission requests through your [contextual policies](https://omnigent.ai/docs/policies/overview). It's built in, so you select the `jcode` harness id directly instead of registering a launch command. ``` omni run --harness jcode ``` Jcode brings **its own auth and model** — Omnigent stores no credential. Jcode ships via a curl installer (no npm package) and owns its provider and model selection in `~/.jcode/config.toml`, so install the CLI and configure a provider there first: ``` curl -fsSL https://jcode.sh/install | bash ``` **Note:** Unlike most builtin ACP harnesses, Jcode's ACP server does **not** support session-scoped MCP — it ignores the `mcpServers` sent in `session/new`. Omnigent therefore does not advertise its builtin MCP tools (session, sub-agent, skill, and policy tools) to Jcode. Configure any MCP servers Jcode should use in its own `~/.jcode/mcp.json` instead. ## Hermes Hermes ships as a builtin harness with the id `hermes` and is offered directly in the web harness picker. Select it like any other builtin — in the picker, via `--harness hermes`, or in agent YAML: ``` executor: harness: hermes model: hermes-4-405b ``` Like the ACP agents, Hermes brings **its own auth** — Omnigent stores no credential and injects none. Hermes reads credentials from its own file-based store (`auth.json` / `.env` under `HERMES_HOME`), so log in and pick a model through its own CLI first: ``` hermes setup # file-based auth under HERMES_HOME hermes model # choose the model Hermes runs ``` Unlike the generic ACP agents, a `/model` pick **is** honored for Hermes: the model you declare in YAML or switch to mid-session threads through to the harness (via `HARNESS_HERMES_MODEL`). The sandbox and workspace a session selects are threaded through as well, so a sandbox chosen in the web session dialog actually confines the Hermes run. ## Kimi Code Kimi Code (`kimi`, alias `kimi-code`) brings **its own auth** — Omnigent stores no credential. Kimi ships via a curl installer (no npm package), so install the CLI and sign in through it first: ``` curl -fsSL https://code.kimi.com/kimi-code/install.sh | bash kimi login # Moonshot OAuth, or paste a Moonshot API key ``` `kimi login` writes its credential to `~/.kimi-code/credentials/kimi-code.json`. `omni setup` detects that file and shows Kimi as **Signed in** once a login has completed; while the CLI is installed but no credential is present it shows **Not configured** with a "Sign in with `kimi login`" hint. Kimi has **no** `kimi logout` subcommand, so `omni setup` offers no sign-out row. To sign out, delete the credential file: ``` rm ~/.kimi-code/credentials/kimi-code.json ``` ## OpenCode The native OpenCode harness (`opencode-native`, alias `opencode`) brings **its own provider auth** — Omnigent stores no OpenCode credential. OpenCode reaches a provider through any of three routes, and `omni setup` reads each one (without touching the secrets) to report which providers OpenCode can reach: 1. **Stored login** — run `opencode auth login`; the credential lands in `~/.local/share/opencode/auth.json`, keyed by provider id. 2. **Provider API-key variables** — a common provider key such as `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `XAI_API_KEY`, `MISTRAL_API_KEY`, or `DEEPSEEK_API_KEY` set in the environment where `omnigent host` runs. 3. **Amazon Bedrock** — see below. A host where none of these is present shows `opencode-native` as **needs-auth** and the web picker disables its row; `omni setup` offers to run OpenCode's native login. ### Amazon Bedrock Bedrock is the exception to the API-key pattern: OpenCode loads it from the AWS credential chain and the AWS SDK authenticates at run time, so there is no `auth.json` entry or provider key to detect. Omnigent treats OpenCode as ready for Bedrock when **either** of the following holds: - Any of these AWS credential-chain variables is set (any non-empty value): `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_PROFILE`, `AWS_REGION`, `AWS_BEARER_TOKEN_BEDROCK`, `AWS_WEB_IDENTITY_TOKEN_FILE`, `AWS_CONTAINER_CREDENTIALS_FULL_URI`, or `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`. `omni setup` lists this as `env: Amazon Bedrock`. - Your OpenCode config (`~/.config/opencode/opencode.json` or `.jsonc`) declares a `provider.amazon-bedrock` block — an empty `{}` is enough. `omni setup` lists this as `config: amazon-bedrock`. ```json { "$schema": "https://opencode.ai/config.json", "provider": { "amazon-bedrock": { "options": { "profile": "work" } } }, "model": "amazon-bedrock/anthropic.claude-sonnet-4-5-20250929-v1:0" } ``` Two things do **not** count on their own: an `enabled_providers` list naming `amazon-bedrock` (it only filters which providers OpenCode shows), and AWS variables OpenCode's Bedrock loader ignores — `AWS_SESSION_TOKEN`, `AWS_DEFAULT_REGION`, and `AWS_SHARED_CREDENTIALS_FILE`. A malformed config file credits nothing. **Note:** The readiness probe only checks that Bedrock is declared — it never contacts AWS. A host can read as ready with a profile or key that AWS later rejects; the failure surfaces when OpenCode makes its first model call. ## Antigravity The native Antigravity harness (`antigravity-native`, launched by `omni agy`) wraps Google's `agy` CLI. It requires **agy 1.1.13 or newer**. Install the CLI first: ``` curl -fsSL https://antigravity.google/cli/install.sh | bash ``` agy speaks the **native Gemini API** (`generateContent` / `streamGenerateContent`, authenticated with an `x-goog-api-key` header). For each session Omnigent resolves the API key and endpoint together, from the first source that applies: 1. The **Gemini default provider** you configured through `omni setup` — a Google API key, a native Gemini gateway, or a Databricks profile (see [Configure native agy credentials](#configure-native-agy-credentials)). 2. The Antigravity API key saved by earlier versions of `omni setup`. 3. A `GEMINI_API_KEY` in the environment where the harness runs, paired with `GOOGLE_GEMINI_BASE_URL` when that is also set (otherwise Google's API root). Both variables are forwarded from the host to the runner. 4. Otherwise agy keeps its own **Google OAuth login** — run `agy` once and complete the browser sign-in. The `agy` CLI has no `agy login` / `agy auth status` subcommand: the token is written on its first interactive run. Omnigent recognizes it at `~/.gemini/oauth_creds.json` (macOS), `~/.gemini/antigravity-cli/antigravity-oauth-token` (Linux), or the macOS Keychain (agy 1.1.7+). Whenever an API key is resolved, Omnigent selects agy's direct Gemini provider (`modelProvider: "gemini"` in the session's isolated agy settings) and the key takes precedence over any saved OAuth login. A configured provider that cannot be resolved — an empty key or an incompatible URL — **fails the session start** with a setup error pointing at `omni setup`, rather than silently falling back to OAuth or ambient credentials. `omni setup` shows the harness as ready once the `agy` binary is present and a credential resolves (or an OAuth login is detected). The Antigravity row names the active credential, or reads **Credential needs setup** when the selected provider can't be resolved. ### Configure native agy credentials Run `omni setup` → **Antigravity** → **Configure native agy API key / gateway** → **Add a credential**, then pick the option matching what you have: | What you have | Setup option | Requirements | | --- | --- | --- | | Google Gemini API key | **Gemini — API key** | A key issued by Google AI Studio; setup supplies Google's API root. | | External native Gemini gateway | **Gemini API gateway — URL + key** | Native Gemini `generateContent` / `streamGenerateContent`, `x-goog-api-key` authentication, and the models agy requests. | | Databricks workspace | **Databricks — profile** | An authenticated Databricks CLI profile, the `databricks` extra, and access to the workspace's native Gemini API and model services. | | OpenAI Responses or Chat Completions endpoint — even one serving Gemini models | Not supported | Would need a protocol adapter, which Omnigent does not provide. Applies to Databricks and external gateways alike. | | Gateway requiring Bearer auth or a token-refresh command | Not supported | agy sends a static `x-goog-api-key`; a `gemini:` provider family may not set `auth_command`. | The first credential you add becomes the Gemini default; adding another keeps the existing default. To switch, select the credential, choose **Make default for Gemini**, and start a **new** `omni agy` session — existing sessions keep their original connection, and `/model` changes the model, not the provider, endpoint, or authentication method. Claude, Codex, and Pi defaults are separate. **Note:** These settings configure native agy only. The in-process `antigravity` SDK harness is a separate integration that keeps its own API-key / Vertex AI setup. #### External Gemini API gateway Setup first asks for a **gateway label** (a name you choose, e.g. `team-gemini`), then the gateway's **native Gemini API root**, the **key** its Gemini route accepts (a gateway virtual key, or a Google key for provider-key pass-through), and optionally an **agy model** (a `--model` value; leave it blank for agy's default). Enter the API root only, for example `https://gateway.example/gemini`. agy appends the operation itself, sending requests to `https://gateway.example/gemini/v1beta/models/:streamGenerateContent`. Setup rejects recognizable mistakes before asking for a key: - a path ending in `/v1beta`, `/openai`, `/responses`, `/chat/completions`, or `/completions`, or containing a `/models/` segment; - an `openai.com` host; - a Databricks workspace host — use **Databricks — profile** instead; - a URL with embedded credentials, a query string, or a fragment. `http://` roots are accepted (useful for a local proxy), and the URL must be reachable from the machine running agy — `localhost` refers to that machine, including when it is a remote host. The key is stored in Omnigent's secret store and referenced from the provider entry. The gateway must preserve Gemini request and response fields, streaming events, tool calls, tool results, and reasoning metadata such as thought signatures, and it must serve the auxiliary Gemini models agy requests — setting the main model does not redirect every auxiliary request to it. Setup validates the URL shape only; it does **not** send an inference request, so a saved credential or a green setup row is not proof the full agent flow works. This option does not refresh expiring upstream tokens. Use the [smoke steps](#verify-the-connection) below. The following gateways document native Gemini frontends. They are documented candidates, not gateways live-verified with agy: | Gateway | API root to enter | Notes | | --- | --- | --- | | [LiteLLM Google AI Studio pass-through](https://docs.litellm.ai/docs/pass_through/google_ai_studio) | `http://127.0.0.1:4000/gemini` for a local proxy | Gemini-native requests and streaming without protocol translation. Configure the Google key on the proxy and use a LiteLLM key in Omnigent; allow every main and auxiliary model agy requests. | | [Bifrost GenAI integration](https://docs.getbifrost.ai/integrations/genai-sdk/overview) | `http://127.0.0.1:8080/genai` for a local instance | Uses a Bifrost virtual key. This frontend adapts requests and responses, so verify tool and reasoning metadata through the full agent flow. | | [Cloudflare AI Gateway Google AI Studio route](https://developers.cloudflare.com/ai-gateway/usage/providers/google-ai-studio/) | `https://gateway.ai.cloudflare.com/v1///google-ai-studio` | Provider-key pass-through uses a Google key. Configurations that require the extra `cf-aig-authorization` header need more header support than the URL-and-key form provides. | OpenAI-compatible routes from the same products are not interchangeable with these native Gemini routes. #### Databricks Choose **Databricks — profile** and enter an existing profile name from `~/.databrickscfg` (or the file selected by `DATABRICKS_CONFIG_FILE`), for example `work`. Do not paste a workspace URL or token into the gateway option — setup rejects Databricks hosts there. This path requires: - the `databricks` extra in the environment running Omnigent (`pip install 'omnigent[databricks]'`) — setup tells you if it's missing; - a **local** profile on the host — managed `connection: databricks` providers are not supported for native Gemini; - an **HTTPS** workspace root in the profile — HTTP hosts, including loopback, are rejected before authentication; - access to the workspace's **native Gemini API** and the corresponding model services. A workspace model page showing `/ai-gateway/mlflow/v1/responses` is an OpenAI Responses endpoint, even when its model is a Gemini service; availability there alone is insufficient. For a CLI OAuth login, sign in and then run setup: ``` databricks auth login --profile work omni setup ``` PAT and service-principal credentials belong in the profile configuration and need no interactive login. Setup authenticates the profile before saving it and stores the provider as `databricks-gemini-` with `native_gemini: true` in `~/.omnigent/config.yaml`; that flag scopes the entry to the Gemini surface only (a regular Databricks profile keeps serving Claude, Codex, and Pi). At launch Omnigent starts a local adapter alongside each agy process. It supplies the profile's Bearer token, refreshes access tokens through the profile's authentication provider, maps agy's model names onto the workspace's Gemini model services, and forwards native Gemini bodies and streaming responses unchanged to: ``` https:///ai-gateway/gemini/v1beta/models/:streamGenerateContent ``` The adapter requires the same main and auxiliary model versions agy requests; an unavailable or ambiguous match fails rather than substituting a different version. It stops with agy and is recreated on resume — you never manage its local URL or session key. The selected profile owns both the workspace and the identity: ambient `DATABRICKS_HOST`, `DATABRICKS_TOKEN`, and OAuth client settings cannot override it, and an invalid profile fails instead of using ambient credentials. If OAuth refresh fails, run `databricks auth login --profile work` again in the same environment. Omnigent does not require `DATABRICKS_AUTH_STORAGE=plaintext` (some CLI versions use it for an older file credential cache); use a consistent storage setting for login and Omnigent, and restart an existing local host daemon after changing its authentication environment. #### Verify the connection Create a small file in your working directory and start a session: ``` printf 'Gateway smoke: file tools work.\n' > gateway-smoke.txt omni agy ``` Ask: *Read gateway-smoke.txt using the file tool and quote its contents.* Confirm a tool call occurs and the reply contains the file's actual text, then ask a follow-up question about it. Exit and run `omni agy --resume` to verify continuity. Repeat in a fresh conversation after switching credentials. - **404** — check both the API route and the availability of the requested model; changing `/model` cannot repair an incompatible API protocol. - **401 / 403** — check the key or profile login and model permissions. Previously saved incompatible entries fail at launch with guidance instead of falling back to ambient credentials. #### Upgrading from an earlier Omnigent - A `gemini:` block in an existing `kind: gateway` or `kind: local` provider becomes usable by native agy. If that provider has `default: true`, Gemini is included in its defaults and takes precedence over agy OAuth; an incompatible URL then fails with a setup error rather than falling back. Update it to a native Gemini API root, or replace `default: true` with an explicit list of its other families (for example `default: [anthropic, openai]`) in `config.yaml` to restore OAuth for new sessions. You can also select another Gemini default in setup. - Setup does not automatically adopt a detected `GEMINI_API_KEY` whose companion `GOOGLE_GEMINI_BASE_URL` is incompatible. Correct the variable and reopen setup; if an earlier setup already saved the rejected URL, edit or remove that saved provider too — changing the environment alone does not replace it. ### Permission prompts Antigravity permission prompts appear in Chat and **Inbox**. You can approve or reject commands and use agy's pattern-based always-allow choices when available. Rules saved through these prompts apply only to the current Omnigent session. ## Codex The native Codex harness (`codex-native`, launched by `omni codex`) decides how a session authenticates before it starts the Codex CLI. In roughly this order, the first source that applies wins: 1. A provider configured for the codex harness — the agent spec's `executor.auth` when you launch a custom agent, otherwise the provider you set up with `omni setup` (API key, gateway, or Databricks). 2. A provider Omnigent auto-detects on the machine when nothing has been configured yet. 3. A **self-sufficient built-in Codex provider** selected by your own Codex `config.toml` (`~/.codex/config.toml`). Codex authenticates these providers itself, so no Omnigent provider and no Codex login is needed. 4. The Codex CLI's own login (`codex login`). ### Use your Codex `config.toml` provider If plain `codex` already runs on your machine because `~/.codex/config.toml` selects a built-in provider that brings its own credentials, `omni codex` routes the same way instead of failing the first turn with *"Codex is not signed in and no Omnigent provider routes the codex harness"*. Every built-in Codex provider except `openai` qualifies — Amazon Bedrock (`amazon-bedrock`, `amazon-bedrock-runtime`), Ollama (`ollama`), and LM Studio (`lmstudio`). An explicit `model_provider = "openai"` still defers to Codex's own login. ```toml # ~/.codex/config.toml model = "openai.gpt-5.6-terra" model_provider = "amazon-bedrock" [model_providers.amazon-bedrock.aws] region = "us-east-1" ``` With this file and no Omnigent provider configured, `omni codex` launches with `model_provider="amazon-bedrock"` and Codex resolves the AWS credential chain itself. The session's provider summary names the built-in provider so you can see which route was taken. A provider configured through `omni setup` (tier 1 above) still takes precedence over the ambient `config.toml` selection. Omnigent also leaves built-in provider tables such as `[model_providers.amazon-bedrock]` untouched when it writes the session's private `config.toml`; only custom `[model_providers.]` tables receive Omnigent's retry settings (`request_max_retries` and friends), because Codex discards the whole config when a built-in table carries fields it doesn't recognize. ### Select a Codex profile with `--profile` The ambient config is read through the `--profile` (or `-p`) you pass to Codex, whether as a CLI pass-through arg or via [`harness.codex-native.args`](#override-the-harness-binary-and-launch-args). Omnigent layers the selected profile over `~/.codex/config.toml` exactly the way Codex does at start: - A file profile at `~/.codex/.config.toml` is layered when it exists — with or without a base `config.toml`. - Otherwise the legacy inline `[profiles.]` table in `config.toml` is used (Codex versions before 0.134 keep profiles inline). So `omni codex --profile bedrock` routes through Bedrock when that profile sets `model_provider = "amazon-bedrock"`, even if the base config doesn't, and `--profile openai` over a Bedrock base keeps Codex's own login. A profile that exists nowhere leaves this tier alone (the launch falls through to Codex login), and a malformed selector — a missing value, a repeated `--profile`, or an invalid profile name — is reported by launch-arg validation when the session starts rather than during routing. ## Pi The native Pi harness (`pi`, alias `pi-native`) runs the Pi CLI against the provider you configured with `omni setup`, writing a managed `models.json` and per-session Pi settings so Pi authenticates with the provider's key rather than its own credential env vars. ### Curated `/model` shortlist A gateway provider family can declare a `models:` map of named tiers. When that map contains **more than one distinct model id**, Omnigent registers every tier with Pi and scopes Pi's `/model` picker to exactly that set, listed under the `omnigent` provider: ```yaml # ~/.omnigent/config.yaml providers: bifrost: kind: gateway default: true openai: base_url: http://bifrost.example.com/v1 api_key: wire_api: chat models: default: GLM-5.3-Flash opus: GLM-5.3 pro: deepseek-v4-pro ``` With this config, `/model` in a Pi session shows `GLM-5.3-Flash`, `GLM-5.3`, and `deepseek-v4-pro` (each once — tiers that resolve to the same id collapse into a single row) and hides Pi's built-in provider catalogs. The scope is a picker preference written to the session's managed Pi settings as `enabledModels`, so you can still toggle the picker back to all models from inside Pi. Two details about how tier values are resolved: - **Aliases.** A tier value may name another tier — for example `default: deepseek-pro` alongside `deepseek-pro: deepseek-v4-pro`. Omnigent follows the alias to the concrete id, so the session launches with `deepseek-v4-pro` and the alias never appears as a picker row of its own. - **Bracket suffixes.** A `[...]` suffix on a tier value (e.g. `GLM-5.3[16m]`) is stripped before the id reaches the gateway or the picker, matching how the launch model is handled. The shortlist only comes from a configured `models:` map. A family with no map, a map that resolves to a single id, or a `--model` override on a default-only setup does **not** scope the picker, and a catalog discovered live from the endpoint never does — in those cases any `enabledModels` preference you already keep in your own Pi settings is left as-is. ### Keep other harnesses' credentials out of Pi Pi activates a built-in provider's whole catalog when it merely sees that provider's credential variable in its environment — whatever the value. A deployment that exports a placeholder token for another harness (for example `ANTHROPIC_AUTH_TOKEN` so `claude-code` can talk to a gateway) would therefore see Pi's picker flooded with built-in entries that bypass the managed provider. Pi's own auth rides the managed `models.json` key and needs none of those variables, so you can name the ones to strip from the Pi terminal's environment with `OMNIGENT_PI_ENV_UNSET`, a comma-separated list of variable names: ``` OMNIGENT_PI_ENV_UNSET=ANTHROPIC_AUTH_TOKEN,OPENAI_API_KEY ``` Names are trimmed and de-duplicated; when the variable is unset or blank, nothing is stripped. Set it in the environment where `omnigent host` runs — the variable carries **names only**, so it is forwarded through both the host daemon and the spawned runner without forwarding the named secrets themselves, and the Pi terminal removes those variables before launching `pi`. Other harnesses in the same deployment are unaffected. ## Custom ACP agents Beyond the built-in harnesses, the generic `acp` harness drives *any* agent that speaks the [Agent Client Protocol](https://agentclientprotocol.com) — the open, editor-agnostic protocol used by Gemini CLI, Qwen Code, Goose, and in-house agents. You point Omnigent at a launch command and it speaks ACP against whatever that command starts. Each ACP agent brings **its own auth** — Omnigent stores no credential, so log into the agent through its own CLI first. Register agents with `omnigent setup` → *configure harnesses* → **Custom ACP agent** → *Add*, providing a name, a launch command, and an optional model. (To pull agents in from an existing OpenClaw/acpx registry instead of typing each one, see [Import agents from OpenClaw](#import-agents-from-openclaw) below.) Some paste-ready commands: | Agent | Command | | --- | --- | | Gemini CLI | `gemini --experimental-acp` | | Qwen Code | `qwen --acp` | | Goose | `goose acp` | Each registered agent is stored in a top-level `acp:` block of `~/.omnigent/config.yaml` and gets a stable slug derived from its name. It then surfaces as its own harness id, `acp:`, in the web picker and in agent YAML: ```yaml acp: agents: - { name: Gemini CLI, command: gemini --experimental-acp } - { name: Goose, command: goose acp, model: gpt-5.3 } ``` ```yaml executor: harness: acp:gemini-cli ``` The agent runs its own agent loop, tools, and context window; Omnigent renders its streaming output, reasoning, and tool cards, routes its permission requests through your [contextual policies](https://omnigent.ai/docs/policies/overview) as web elicitation cards, and honors interrupts. Omnigent also exposes its own builtin tools (session, sub-agent, skill, and policy tools) to the agent alongside the agent's native tools; set `OMNIGENT_ACP_MCP=0` to disable that bridge for a session. To disable that MCP relay permanently for a specific agent, set `omnigent_mcp: false` on its `acp:` entry. The agent keeps its own native tools; Omnigent simply stops lending its builtin tools to that agent in `session/new` (the field is still sent as an empty list, which ACP requires). This is required for agents whose ACP server rejects per-session MCP servers — such as the OpenClaw Gateway bridge described below. The value must be a boolean; `omnigent setup` reports an error if it isn't. ```yaml acp: agents: - name: OpenClaw command: openclaw acp --url --token-file omnigent_mcp: false ``` **Note:** The generic ACP harness has no fixed binary — each agent owns its own install and login, so a missing binary is a hint, not a hard gate. A sandboxed generic ACP agent gets read access to its binary directory, the cwd, and write access to `/tmp`; an agent that must write its own config directory needs `sandbox: none` (the default) for now. ### Import agents from OpenClaw If you already keep an agent registry for OpenClaw or the `acpx` CLI, Omnigent can import those agents into its `acp:` block instead of re-adding each one by hand. Run `omnigent setup` → *configure harnesses* → **Import from OpenClaw**. The row shows how many agents were detected automatically. Omnigent looks in two default locations: | Source | Path | | --- | --- | | acpx | `~/.acpx/config.json` | | OpenClaw | `~/.openclaw/openclaw.json` | Both files are read as JSON5, and you can pick *Choose another file…* to point at a config elsewhere. Omnigent lists the agents it found, derives a stable slug from each name, and — after you confirm — appends them to the `acp:` block of `~/.omnigent/config.yaml`. From then on they behave exactly like manually added ACP agents. Import stores **only the launch command, not credentials** — each agent still authenticates through its own CLI. Re-running the import is safe: agents already present are skipped, and a new agent whose name collides with an existing slug gets a numbered suffix (e.g. `gemini-cli-2`) so nothing is overwritten. An agent whose binary isn't on your `PATH` is flagged as a hint, not blocked. To launch a single OpenClaw/acpx agent once **without** saving it to your config, use `--from-openclaw` on `omni run`: ``` omni run --from-openclaw "Gemini CLI" -p "review the last commit" ``` Match the agent by its display name (case-insensitive) or derived slug. This runs the agent through the `acp` harness for that one session; it can't be combined with an `AGENT` path or with `--harness`. This path drives the selected coding agent directly. It does **not** bring OpenClaw's Gateway session, routing, memory, or channels into the conversation — for that, register the Gateway bridge (below). ### Drive the OpenClaw Gateway Beyond importing individual coding agents, the `openclaw acp` command exposes a live OpenClaw Gateway session as an ACP server over stdio, giving Omnigent access to OpenClaw's own routing, memory, and channels. Register it as an `acp:` agent, disabling Omnigent's MCP relay because the Gateway bridge rejects per-session MCP servers: ```yaml acp: agents: - name: OpenClaw command: openclaw acp --url --token-file omnigent_mcp: false ``` Replace `` and `` with your running Gateway's connection details, then launch it: ``` omni run --harness acp:openclaw ``` **Caution:** Prefer `--token-file` so the Gateway token isn't stored in the launch command. Don't commit or share `~/.omnigent/config.yaml`, and use a token with the narrowest permissions OpenClaw supports. Omnigent owns the outer conversation and transcript; OpenClaw owns the Gateway-backed session. OpenClaw's Control UI is a separate Gateway client, so messages entered there while Omnigent is offline are **not** imported into the Omnigent conversation — use Omnigent as the canonical conversation when you need its transcript and resume behavior. Bidirectional sync with the Control UI and per-session Omnigent MCP are not supported by this integration. ## Swap harnesses Tools, policies, and other config stay the same across harnesses. Just change the `harness` value in your YAML, or override it at runtime: ``` omni run agent.yaml --harness codex ``` See [Models & Credentials](https://omnigent.ai/docs/build/models) for how to set up API keys and choose models. ## Override the harness binary and launch args By default each harness launches its vendor CLI from your `PATH` (e.g. `claude`, `codex`). You can point a harness at a specific executable, or prepend base launch args, from your user-level Omnigent config, `~/.omnigent/config.yaml`. A project's `.omnigent/config.yaml` can select the default harness but cannot set per-harness commands or args. View the config with `omni config list`; set a value with `omni config set --global =`. The top-level `harness:` config key is polymorphic. The legacy scalar form still works (and auto-migrates to the mapping form the next time the config is written): ```yaml # Legacy scalar (deprecated, still honored): harness: claude-sdk # Mapping form — a default plus per-harness startup overrides: harness: default: claude-sdk codex-native: command: /usr/local/bin/codex args: [--config, approval_policy=on-request] pi-native: command: /opt/bin/pi ``` - `default` (optional) — the default harness id for `omni run`. - `command` (optional) — overrides the vendor CLI executable for that harness. - `args` (optional list) — base launch args; any CLI pass-through args are appended *after* these. This top-level `harness:` config key selects the CLI default and per-binary launch overrides. It's separate from the `executor.harness` value inside an agent's own YAML, which pins the harness for that one agent. **Note:** Harness executables and launch args are user-owned. In a project's `.omnigent/config.yaml`, only the harness selection is honored, either as the scalar form (`harness: codex-native`) or as `harness.default`. Your user-level per-harness `command`/`args` entries still apply. Any per-harness entries in the project file (for example `harness.codex-native`) are ignored with a warning, and `omni config list` lists them as `# ignored project harness launch overrides: ...` and tells you to move them to `~/.omnigent/config.yaml`. ### Binary-path precedence For a harness's `command`, the first non-empty source wins: 1. The `OMNIGENT__PATH` environment variable. 2. The config `harness..command`. 3. The harness's built-in default executable. `args` follow the same idea: the config `args` are the base, and CLI pass-through args are appended after them. `` is the harness's underlying *binary*, not its id: the `-native` suffix is stripped, so `pi` and `pi-native` share `OMNIGENT_PI_PATH`, and the Claude harnesses map to `OMNIGENT_CLAUDE_PATH` (so `claude-sdk` and `claude-native` share one var). **Deprecated:** The older `HARNESS__PATH` env vars (for `codex`, `pi`, `kimi`, `goose`, `qwen`, and `hermes`) are still read as a fallback but log a warning and will be removed in **v0.8.0** — use `OMNIGENT__PATH` instead. The `omni claude --command` flag is likewise deprecated (set `OMNIGENT_CLAUDE_PATH` or `harness.claude-native.command` instead) and will be removed in a future release. No other native command has a `--command` flag. To add a runtime that Omnigent does not include, see [Community Harnesses](https://omnigent.ai/docs/build/harnesses/community). --- ## https://omnigent.ai/docs/build/harnesses/supported # Supported Harnesses Every harness on this page is supported. The Support column describes how the integration is maintained and how urgently the Omnigent team responds when it breaks; it does not indicate whether the harness works. ## Support - **Fully supported.** Every Omnigent feature is expected to work well with these harnesses. Their integrations are heavily tested in CI and manually by the team. A regression is treated as a serious, blocking bug. - **Maintained.** These harnesses are tested in CI and their features are verified to work with Omnigent, but the team gives them less focus than Fully supported harnesses. Bugs are fixed in the normal course of work and shipped with the next release. - **Community-supported.** These harness integrations are primarily maintained by the community. They can work reliably through shared direct, ACP, and native-TUI paths, and community contributions are welcome. A Community-supported harness can become Maintained or Fully supported as it gains dedicated integration coverage, CI testing, and active maintenance from the Omnigent team. ## Execution modes Most coding agents can run in one of two modes: - **Direct** — Omnigent drives the agent's model and tools itself. You get the full platform: web UI, streaming, [contextual policies](https://omnigent.ai/docs/policies/overview), persistent sessions, and mobile. - **Native TUI** — Omnigent boots the vendor's own terminal UI in a pane and mirrors it back. You get the exact native experience, wrapped with Omnigent's collaboration and policy layer. These ids end in `-native`. ## Harnesses | Harness | IDs and aliases | Direct execution | Native TUI | Support | Repository | | --- | --- | --- | --- | --- | --- | | Claude Code | `claude-sdk` (`claude`), `claude-native` | Yes | Yes | Fully supported | — | | Codex | `codex`, `codex-native` | Yes | Yes | Fully supported | — | | OpenAI Agents SDK | `openai-agents` (`openai-agents-sdk`) | Yes | No | Fully supported | — | | Copilot | `copilot` | Yes | No | Maintained | — | | Cursor | `cursor`, `cursor-native` | Yes | Yes | Maintained | — | | OpenCode | `opencode-native` (`opencode`) | No | Yes | Maintained | — | | Pi | `pi`, `pi-native` | Yes | Yes | Maintained | — | | Antigravity | `antigravity`, `antigravity-native` | Yes | Yes | Community-supported | — | | Devin | `devin-native` (`devin`, `native-devin`; `devin-acp` removed in 0.14) | No | Yes | Community-supported | — | | Hermes | `hermes`, `hermes-native` | Yes | Yes | Community-supported | — | | Kimi | `kimi` (`kimi-code`), `kimi-native` | Yes | Yes | Community-supported | — | | Kiro | `kiro-native` | No | Yes | Community-supported | — | | Qwen Code | `qwen` (`qwen-code`), `qwen-native` | Yes | Yes | Community-supported | — | | Rovo Dev | `rovo` (`rovo-cli`) | Yes | No | Community-supported | [shbhmrzd/omnigent-rovo](https://github.com/shbhmrzd/omnigent-rovo) | | ZCode | `zcode` (`z-code`) | Headless print mode | No | Community-supported | [anxkhn/omnigent-zcode](https://github.com/anxkhn/omnigent-zcode) | Pi is a headless multi-model worker that runs on any gateway model — ideal for review, exploration, and read-heavy tasks delegated by a supervisor agent. ZCode requires the separate [community plugin](https://github.com/anxkhn/omnigent-zcode) and a configured ZCode CLI. See the plugin README for installation, supported features, and print-mode limitations. ## Harnesses supported via ACP Some built-in harnesses use Omnigent's ACP integration under the hood rather than having a separate support designation. | Harness | IDs and aliases | Direct execution | Native TUI | | --- | --- | --- | --- | | Goose | `goose`, `goose-native` | Yes | Yes | | Grok Build | `grok` (`grok-build`) | Yes | No | | Jcode | `jcode` | Yes | No | See [Custom ACP agents](https://omnigent.ai/docs/build/harnesses/configuration#custom-acp-agents) for ACP configuration details. See [Configuration](https://omnigent.ai/docs/build/harnesses/configuration) to set up and run one of these harnesses. --- ## https://omnigent.ai/docs/build/models # Models & Credentials Every harness needs a model to run and credentials to reach it. You declare the model in your agent YAML, and Omnigent manages the credentials that authenticate to each provider. ## Choose a model Declare the model in your agent YAML: ``` executor: harness: claude-sdk model: claude-sonnet-4-6 ``` Override it at runtime without editing the file: ``` omni run agent.yaml --model claude-sonnet-4-6 ``` ### Switch models mid-session Use the `/model` command inside any running session to switch models on the fly. The agent keeps its conversation history and tool state – only the underlying model changes. For the Pi harness, a gateway provider's `models:` tier map can scope the `/model` picker to a curated shortlist — see [Pi configuration](https://omnigent.ai/docs/build/harnesses/configuration#pi). ## Reasoning effort Set a default reasoning level for the agent alongside the model: ``` executor: harness: claude-sdk model: claude-sonnet-4-6 reasoning_effort: high # low | medium | high | xhigh ``` `executor.reasoning_effort` is optional. When unset, the harness CLI's own default applies. The accepted values are `low`, `medium`, `high`, and `xhigh`; which ones a given harness actually honors is harness-dependent, and the value is validated against the harness's effort vocabulary at launch (an unknown value is also rejected up front when the spec is validated). The spec-level default applies to the main session **and** to every sub-agent dispatch that doesn't pass its own effort. Precedence, highest first: 1. A per-dispatch argument on `sys_session_send` / `sys_session_create`. 2. `executor.reasoning_effort` from the spec. 3. The harness CLI default. In the Web UI, `claude-sdk` and `codex` sessions also expose an in-session **Effort** dial that overrides the spec default for that session and applies on the next turn without losing history — see [Change reasoning effort mid-session](https://omnigent.ai/docs/interact/web-ui#change-reasoning-effort-mid-session). **Back-compat:** the older `llm.reasoning_effort` key is still accepted and is lifted to `executor.reasoning_effort` when the spec is parsed. Prefer `executor.reasoning_effort` in new specs; when both are set, `executor.reasoning_effort` wins. ## Credentials Omnigent needs credentials to talk to a model provider. ``` omni setup ``` The setup wizard walks you through adding a credential, setting a default, or removing one. It also auto-detects credentials already on your machine (API keys in environment variables, logged-in CLIs, and local servers) so you can confirm them with one tap. ## Credential types | Type | What it is | | --- | --- | | **API key** | A first-party provider key (Anthropic, OpenAI, OpenRouter, and more) | | **Subscription** | A Claude Pro/Max or ChatGPT Plus/Pro plan, via the official `claude` / `codex` CLIs | | **Gateway** | Bring your own proxy or local model. Just paste a base URL and key. | | **Databricks** | A Databricks workspace profile, routing through the [Foundation Model API](https://docs.databricks.com/aws/en/machine-learning/foundation-model-apis/) — or, for native Antigravity, the workspace's native Gemini API | ### API key Get a key from your provider's dashboard and add it via `omni setup`. Supported providers: | Provider | Key source | | --- | --- | | **Anthropic** | [console.anthropic.com](https://console.anthropic.com) | | **OpenAI** | [platform.openai.com](https://platform.openai.com) | | **OpenRouter** | [openrouter.ai](https://openrouter.ai) | | **Groq** | [console.groq.com](https://console.groq.com) | | **DeepSeek** | [platform.deepseek.com](https://platform.deepseek.com) | | **xAI** | [console.x.ai](https://console.x.ai) | | **Mistral** | [console.mistral.ai](https://console.mistral.ai) | | **Together AI** | [api.together.xyz](https://api.together.xyz) | | **Fireworks AI** | [fireworks.ai](https://fireworks.ai) | ### Subscription If you have a **Claude Pro/Max** or **ChatGPT Plus/Pro** plan, Omnigent can use your existing CLI login. No API key needed. Just make sure you're logged in: ``` claude auth login # for Claude Pro/Max codex login # for ChatGPT ``` `omni setup` auto-detects an active CLI login and offers it as a one-tap option. For Codex, a `~/.codex/config.toml` that selects a self-authenticating built-in provider (Amazon Bedrock, Ollama, LM Studio) also works with no login and no Omnigent provider — see [Codex configuration](https://omnigent.ai/docs/build/harnesses/configuration#codex). ### Gateway Omnigent works with any **OpenAI-compatible** or **Anthropic-compatible** gateway: Supported gateways: - Databricks Unity AI Gateway - MLflow AI Gateway - OpenRouter - LiteLLM - Portkey - Helicone - Cloudflare AI Gateway - Kong AI Gateway - Vercel AI Gateway - Azure OpenAI - Ollama - LM Studio - vLLM - LocalAI - TGI (Hugging Face) - GPT4All For the native Antigravity harness (`omni agy`), a gateway must instead serve the **native Gemini API** — OpenAI-compatible routes are not accepted, even when they serve Gemini models. Add one under `omni setup` → **Antigravity** → **Configure native agy API key / gateway**; see [Antigravity](https://omnigent.ai/docs/build/harnesses/configuration#antigravity) for the URL rules and compatibility requirements. `omni setup` asks for a **base URL** and a **key**. The base URL differs depending on the agent: | Provider | For | Base URL | Key | | --- | --- | --- | --- | | **OpenRouter** | Claude Code | `https://openrouter.ai/api` | your OpenRouter key (`sk-or-...`) | | **OpenRouter** | Codex / OpenAI agents | `https://openrouter.ai/api/v1` | your OpenRouter key (`sk-or-...`) | | **Ollama** (local) | Codex / OpenAI agents | `http://localhost:11434/v1` | any value (Ollama ignores it) | **Note:** For Claude Code, point at OpenRouter's Anthropic-compatible endpoint (`.../api`, **not** `.../api/v1`); for Codex and the OpenAI-agents harness, use the OpenAI-compatible `.../api/v1`. #### Cost tracking for self-hosted models Self-hosted and gateway models (Ollama, vLLM, custom endpoints) often aren't in Omnigent's pricing catalog, so their sessions show no cost. Add a `pricing` block to a provider family in your config to tell Omnigent what the model costs, and it will track spend for those sessions like any catalog model. ```yaml # ~/.omnigent/config.yaml providers: ollama-local: kind: local openai: base_url: http://localhost:11434/v1 api_key: ollama pricing: input_per_million: 0.0 output_per_million: 0.0 ``` Prices are specified **per million tokens** in USD. `input_per_million` and `output_per_million` are required; `cache_read_per_million` and `cache_write_per_million` are optional. When the cache rates are omitted, Omnigent derives them from the input rate using standard fallback ratios (cache read ≈ 0.10×, cache write ≈ 1.25×). All values must be `>= 0`. ```yaml pricing: input_per_million: 0.25 output_per_million: 1.0 cache_read_per_million: 0.025 cache_write_per_million: 0.3125 ``` Configured pricing takes **precedence** over the catalog, so a gateway that serves a catalog-known model id (for example `claude-sonnet-4-6`) under its own rates is priced with your configured values rather than the vendor's. Pricing is attached to the provider family (`anthropic` or `openai`) that the session's harness uses. **Note:** Custom pricing is resolved from the default provider for the session's harness. Sessions that select a named provider differing from the default may not be priced with these rates. ### Databricks Route models through your Databricks workspace using the [Foundation Model API](https://docs.databricks.com/aws/en/machine-learning/foundation-model-apis/). Omnigent resolves credentials from your `~/.databrickscfg` profile. ``` executor: harness: claude-sdk model: databricks-claude-sonnet-4-6 auth: type: databricks profile: ``` Model names are prefixed with `databricks-` so Omnigent routes them through your workspace automatically. See the [Databricks Foundation Model API docs](https://docs.databricks.com/aws/en/machine-learning/foundation-model-apis/supported-models) for the full list of available models. To route the native Antigravity harness (`omni agy`) through a workspace's native Gemini API, add the profile with **Databricks — profile** under `omni setup` → **Antigravity** → **Configure native agy API key / gateway** instead; the resulting provider carries `native_gemini: true` and serves only the Gemini surface. See [Antigravity → Databricks](https://omnigent.ai/docs/build/harnesses/configuration#databricks). --- ## https://omnigent.ai/docs/build/prompts # Prompts & Skills An agent's behavior starts with its system prompt. Skills extend that by providing reusable instruction sets the agent can load on demand. ## System prompt Set your agent's system prompt with either `prompt` or `instructions`. Both take inline text; `instructions` also accepts a file path. ``` # Inline text (either field): prompt: You are a concise coding assistant. # Or equivalently: instructions: You are a concise coding assistant. # From a file (instructions only): instructions: prompts/system.md ``` `instructions` also accepts file paths (resolved relative to the agent YAML's directory). If you set both fields, `instructions` wins. ### Auto-discovery When neither `prompt` nor `instructions` is set, Omnigent scans the agent directory for context files in this order: 1. `AGENTS.md` 2. `CLAUDE.md` 3. `.cursorrules` The first file found becomes the system prompt. No merge, first wins. ## Skills Skills are reusable instruction bundles that your agent can load at runtime via the `/skill-name` slash command or the built-in `load_skill` tool. Each skill is a markdown file with structured instructions. Think of it as a recipe the agent follows for a specific task. ### How skills are discovered Omnigent discovers skills from two sources: **1. Bundled skills**, shipped with the agent itself: ``` my-agent/ config.yaml skills/ code-review/ SKILL.md deploy/ SKILL.md ``` Skills in the agent's `skills/` directory are always available. The agent can invoke each skill by its slash command, such as `/code-review`, or load it with `load_skill`. **2. Project and user skills**, discovered from the filesystem: Omnigent walks up the directory tree from the agent's working directory, looking for skills in `.agents/skills/` and `.claude/skills/` at each level, plus your home directory for global skills: - `/.agents/skills/`: available to any agent run from this project - `/.claude/skills/`: same, Claude Code convention - `~/.agents/skills/`: available to every agent on your machine - `~/.claude/skills/`: same, Claude Code convention This works like `.gitignore`. Drop a skill folder in your project and every agent picks it up automatically. ### Existing Claude Code skills If you already have skills in `.claude/skills/`, they work alongside Omnigent bundled skills automatically. Omnigent discovers them during its directory walk. One caveat: Omnigent's skill loader reads only `name`, `description`, and `user-invocable` from SKILL.md frontmatter. Claude Code's additional frontmatter fields are handled differently depending on your harness: | Frontmatter field | Omnigent skill loader | `claude-native` harness | | --- | --- | --- | | `name` | Parsed | Parsed | | `description` | Parsed | Parsed | | `user-invocable` | Parsed | Parsed | | `disable-model-invocation` | Ignored | Honored | | `allowed-tools` | Ignored | Honored | If you need `disable-model-invocation` or `allowed-tools` behavior, use the `claude-native` harness and place those skills in `.claude/skills/`. ### Existing Codex skills and plugins On the Codex harnesses (`codex` and `codex-native`), Omnigent also surfaces the skills Codex itself knows about, so they appear in the skills menu alongside your bundled and discovered skills: - **Standalone Codex skills** in the Codex home's `skills/` directory. These are listed by **directory name** (the name Codex registers the slash command under), not by the `name` in their frontmatter. - **Skills from installed Codex plugins.** Omnigent runs `codex plugin list --json` against the session's Codex home and loads skills from every plugin that is installed **and enabled**, reading the exact version the inventory reports from Codex's plugin cache. Disabled plugins and stale cached versions are hidden; updating a plugin in Codex changes what Omnigent lists on the next discovery. Plugin skills are namespaced as `:` — for example a `review` skill from a plugin whose manifest is named `toolkit` shows up as `toolkit:review`. The namespace is the `name` from the plugin manifest (`.codex-plugin/plugin.json`, or `.claude-plugin/plugin.json` for plugins authored for Claude Code); the skill part is the `name` from the skill's SKILL.md frontmatter. Within a plugin, skills are read from the manifest's `skills` paths (a `./`-relative path or list of paths, each either a single skill folder or a directory of skill folders), falling back to the plugin's `skills/` directory, plus any commands Codex migrated into `.codex-plugin/migrated-command-skills`. A few practical notes: - `codex-native` sessions use the session's Codex home; the in-process `codex` (SDK) harness uses `~/.codex`. - Plugin discovery needs the Codex CLI on the machine running the agent. If the CLI is missing, times out (5 seconds), or returns something unparseable, plugin skills are simply omitted — standalone Codex skills and everything else keep working. - A plugin with a broken manifest or an unreadable SKILL.md is skipped with a warning in the logs; other plugins are unaffected. - With `skills: none`, Omnigent never queries the plugin inventory. ### SKILL.md format Each skill lives in its own folder and must have a `SKILL.md` file with YAML frontmatter: ``` skills/ my-skill/ SKILL.md references/ # optional resource files style-guide.md scripts/ # optional scripts assets/ # optional assets ``` The `SKILL.md` file has two parts: YAML frontmatter and markdown content. ``` --- name: code-review description: >- Review code changes for correctness, tests, security, and maintainability. Use when asked to review a PR or diff. --- # Code review ## Procedure 1. Read the diff carefully. 2. Check for correctness and edge cases. 3. Verify tests cover the changes. 4. Flag security concerns. 5. Summarize findings as blocking / non-blocking / suggestions. ## What to look for - Off-by-one errors - Missing error handling - Untested branches ... ``` **Required frontmatter fields:** | Field | Description | | --- | --- | | `name` | Unique skill identifier. Used in slash commands (`/code-review`) and `load_skill` calls. | | `description` | One-line description. Shown to the agent so it knows when to use the skill. | The markdown body after the frontmatter is the full instruction set. It can be as long and detailed as needed. ### Resource files Skills can bundle reference materials in `references/`, `scripts/`, and `assets/` subdirectories. When a skill is loaded, the agent sees a listing of available files and can read them with the `read_skill_file` tool: ``` skills/ deploy/ SKILL.md references/ runbook.md checklist.md scripts/ validate.sh ``` ### Example: Polly's skills The [Polly orchestrator](https://github.com/omnigent-ai/omnigent/tree/main/examples/polly) bundles three skills that define its workflows: ``` examples/polly/ config.yaml skills/ investigate/ SKILL.md # read-only investigation workflow fanout/ SKILL.md # parallel task dispatch cross-review/ SKILL.md # cross-vendor PR review ``` Each skill defines a step-by-step procedure the orchestrator follows. For example, the `investigate` skill instructs Polly to decompose a question into bounded tasks, dispatch sub-agents, and synthesize their reports. ### Filter discovered skills By default, all project and user skills are loaded (`skills: all`). You can restrict this in your agent YAML: ``` # Load all discovered skills (default): skills: all # Load no discovered skills (hermetic): skills: none # Load only specific discovered skills by name: skills: - code-review - deploy ``` This only affects discovered skills (from `.agents/skills/` and `.claude/skills/`, and on the Codex harnesses, standalone Codex skills and Codex plugin skills). Bundled skills in the agent's own `skills/` directory are always available regardless. For Codex plugin skills, a list entry matches either the bare skill name or the namespaced form, so both `review` and `toolkit:review` select `toolkit:review`. A bare name selects that skill from every enabled plugin that provides one. --- ## https://omnigent.ai/docs/build/routing # Smart Routing Smart routing lets the server pick the best harness and model for a session **automatically**, based on the first message. Instead of committing to one model up front, the server asks a router, "given this task, which harness and model fit?", and applies the answer for the rest of the session. [Video](https://omnigent.ai/images/docs/smart-routing-oss.mp4) Two routers are available: - **Built-in judge**: an LLM call using the server's own `llm:` block. No extra infrastructure. - **External routing API**: the server delegates the decision to an external `routes:select` service that you point it at. ## Enable smart routing Smart routing is enabled by configuration alone: - Configure a server `llm:` block and the server uses the **built-in judge** backed by it, with no `routing:` block required. - Or add a `routing:` block with `provider: external` to delegate to an **external routing API**. For example, this enables the built-in judge with an OpenAI model: ```yaml llm: model: openai/gpt-5.5 connection: api_key: ${OPENAI_API_KEY} ``` The API key may also come from the provider's usual environment variables. See [built-in routing settings](https://omnigent.ai/docs/reference/configuration/routing#built-in-routing) for custom endpoints, Databricks profiles, and fallback models. Start the server with your config file: ``` omni server -c path/to/config.yaml ``` With a router configured, users see an **Auto** option in the harness picker; picking it defers harness + model selection to the router on the first message. With neither an `llm:` block nor an external `routing:` block, routing stays off and the Auto option is hidden. ## Configure the external routing API To delegate routing to an external service, add a top-level `routing:` block to the server config with `provider: external`: ```yaml routing: provider: external base_url: https://gateway.example.com/ai-gateway/routing/v1 router_name: task_v0 model_prefix: databricks- api_key: ${ROUTING_API_KEY} ``` See [external routing settings](https://omnigent.ai/docs/reference/configuration/routing#external-routing) for all fields and validation behavior, and [authentication precedence](https://omnigent.ai/docs/reference/configuration/routing#authentication-precedence) for choosing between an API key and a Databricks profile. ## Implement an external router The server calls `POST /routes:select` with the candidate harness and model combinations, the task, and the requested routing strategy. A typical [request](https://omnigent.ai/docs/reference/routing-api#request-selectrouterequest) looks like this: ```json { "route_options": [ { "model": "claude-opus-4-8", "harness": "claude-sdk" }, { "model": "gpt-5-5", "harness": "codex" }, { "model": "gpt-5-4-mini", "harness": "pi" } ], "task": { "prompt": "Refactor the auth module and add tests" }, "route_selector": { "router_name": "task_v0" } } ``` Return a [response](https://omnigent.ai/docs/reference/routing-api#response-selectrouteresponse) with one of the offered model and harness combinations: ```json { "route_selection": [ { "route_option": { "model": "claude-opus-4-8", "harness": "claude-sdk" } } ], "rationale": "Multi-file refactor with tests: favor the most capable model." } ``` Implement the [external routing API contract](https://omnigent.ai/docs/reference/routing-api) for the complete schema, matching rules, and error behavior. If the service returns an error or an invalid selection, the server surfaces the reason and falls back to the session's default harness rather than blocking it. --- ## https://omnigent.ai/docs/build/scheduled-tasks # Scheduled Tasks A scheduled task is a saved prompt that fires an agent session on a recurring schedule. Point it at an agent, a prompt, a recurrence rule, and a place to run, and Omnigent launches that agent automatically on every occurrence — no one has to be at the keyboard. Scheduled tasks are managed through four built-in tools that **every agent has automatically**. They are framework-owned and auto-registered, so an agent can create, list, update, and delete its own recurring runs at runtime without the agent YAML opting in. Under the hood the runner dispatches each tool to the Omnigent server's `/v1/scheduled-tasks` REST endpoints. ## The built-in tools | Tool | What it does | | --- | --- | | `sys_scheduled_task_create` | Create a recurring task. | | `sys_scheduled_task_list` | List your scheduled tasks with their schedules and state. | | `sys_scheduled_task_update` | Update a task's mutable fields. | | `sys_scheduled_task_delete` | Delete a task so it no longer fires. | Tasks are scoped to the calling user (or the single `local` user when auth is disabled). You only see and manage your own tasks; a task owned by someone else is not enumerable. ## Approval for create and update Because a scheduled task persists work that will later run unattended, an agent cannot save or change one silently. Every call to `sys_scheduled_task_create` or `sys_scheduled_task_update` pauses for your interactive approval before the task is written: > Approve these scheduled task settings for future unattended runs? Review the agent, prompt, host, and permission settings in that request before accepting. The gate fires even when the call omits optional fields — inherited defaults (for example, the host resolved at fire time or the agent's own model and permission mode) are persisted too, so they also need your consent. Declining denies the call and nothing is saved. This check is an always-on guard, not something you configure: it is appended to every session's policy set — including sub-agent sessions — even when no [policies](https://omnigent.ai/docs/policies/overview) are declared anywhere, and it cannot be removed. See [Always-on approval guards](https://omnigent.ai/docs/policies/builtin#always-on-approval-guards). `sys_scheduled_task_list` and `sys_scheduled_task_delete` do not trigger this prompt. Tasks you create or edit directly in the [web UI](https://omnigent.ai/docs/interact/web-ui#scheduled-tasks) or through the `/v1/scheduled-tasks` REST API keep their existing authorization rules. ## Creating a task `sys_scheduled_task_create` takes these fields: - **`name`** (required) — a human-readable task name, e.g. `nightly triage`. - **`prompt`** (required) — the instruction dispatched to the agent on each firing. - **`rrule`** (required) — the recurrence rule (see [Recurrence rules](#recurrence-rules)). - **`agent_id`** (required) — the agent to run, e.g. `ag_abc123` (from `sys_agent_list` or `sys_agent_get`). - **`execution_target`** (optional) — where each firing runs: `connected_host` (default) or `managed_sandbox`. See [Where a task runs](#where-a-task-runs). - **`host_id`** (optional, `connected_host` only) — pin the run to a specific connected host, from the current workspace's host list. Omit to run on your online host at fire time; a failed run is recorded if none is online. An existing managed sandbox host cannot be pinned. - **`workspace`** (optional, `connected_host` only) — an existing absolute path on the host where the fired session's runner starts. Requires `host_id`; omit it to start in the host's home directory. - **`timezone`** (optional) — the IANA timezone the rule is evaluated in, e.g. `America/Los_Angeles`. Defaults to `UTC`. - **`model_override`** (optional) — a per-run model override. Omit for the agent default. - **`reasoning_effort`** (optional) — a per-run reasoning-effort hint, e.g. `high`. Omit for the agent default (the spec's [`executor.reasoning_effort`](https://omnigent.ai/docs/build/models#reasoning-effort), or the harness default when the spec sets none). Once created, the task fires automatically on its schedule until you delete it. ## Recurrence rules The schedule is an [RFC 5545](https://icalendar.org/) `RRULE` string, evaluated in the task's `timezone`. Examples: ``` FREQ=DAILY;BYHOUR=9;BYMINUTE=0 # daily at 9am FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR;BYHOUR=9;BYMINUTE=0 # weekday mornings ``` Rules are validated when you create or update a task. A rule is rejected (HTTP 400) if it has bad syntax, never fires, fires only once, or fires more often than the minimum interval. **A task must fire at least twice and no more often than once per hour** — the minimum gap between any two consecutive fires is 60 minutes. ## Pausing, updating, and deleting `sys_scheduled_task_update` changes only the fields you pass; the rest are left untouched. You can update `name`, `prompt`, `rrule`, `timezone`, `model_override`, `reasoning_effort`, `workspace`, `host_id`, `execution_target`, and `state`. Passing `execution_target=managed_sandbox` switches the task to a fresh sandbox per run and clears any pinned `host_id` and `workspace`; don't set either field in the same update. Switching back to `connected_host` leaves the task unpinned until you set a host again. The `state` field accepts `active` or `paused`: - Pass `state=paused` to stop a task firing without deleting it. - Pass `state=active` to resume it. To remove a task permanently, use `sys_scheduled_task_delete` — deletion is not done through `update`. ## Where a task runs `execution_target` picks the machine each firing runs on. ### `connected_host` (default) The task runs on one of your own connected hosts. Pin a specific host with `host_id` (and optionally a `workspace` on it), or leave both unset and Omnigent resolves your most-recently-active **online** host when the task fires, starting in that host's home directory. Server-managed sandbox hosts are never picked by this resolution, and pinning one as `host_id` is rejected — a warm sandbox from an interactive session would leak its state into the run and break once it is gone. If no live host can be resolved (or the pinned host is offline), the occurrence is recorded as a failed run. ### `managed_sandbox` Each firing provisions a **fresh** server-managed sandbox — the same launch path as choosing **New Sandbox** for a chat (see [Cloud Sandbox Host](https://omnigent.ai/docs/deploy/cloud-sandbox-host)). The session starts in the sandbox's clean server-created workspace, so `host_id` and `workspace` must not be set; a create or update that combines them with `managed_sandbox` is rejected. The server must have managed sandboxes configured — an unconfigured server rejects the task at create time rather than failing every fire. A sandbox created by an automation follows the server's normal sandbox lifecycle, exactly like one created for a chat: the automation does not override timeouts or terminate the sandbox when the run finishes. Server operators should choose provider lifetime, idle, and cleanup settings that fit their automation frequency and resource budget; enabling managed sandboxes alone does not guarantee a short shutdown time. ## How a firing runs When a task is due, Omnigent: 1. Re-reads the task row — a task that was deleted or paused since the timer was armed is skipped. Under built-in accounts auth, the run also confirms the task's owner is still the same registered account and that the target `host_id` belongs to that owner; a task whose owner account has been removed is marked deleted and the run is recorded with error code `owner_deleted`. 2. Resolves the launch target for this fire only. A `connected_host` task fills in an unpinned host and workspace as described above; a `managed_sandbox` task starts hostless and binds a new sandbox in step 4. Resolved values are never written back to the task. 3. Creates a session bound to the task's agent, carrying the stored `model_override` and `reasoning_effort`, and grants the task's owner ownership of the new session so the run is visible. 4. Launches the runner — on the resolved host, or in a freshly provisioned sandbox — and dispatches the prompt so the agent actually runs. 5. Records the run — stamping the task's last-run time and the run's conversation id. A few scheduling guarantees: - **The database is the source of truth.** On server start, every active task is re-armed from the store. - **Missed fires are not replayed.** If the server was down when a task was due, only the next future occurrence is armed — Omnigent does not backfill. - **Overlapping fires are skipped.** If a task's previous run is still in flight when the next occurrence arrives, that occurrence is dropped. - **Removed accounts stop firing.** When an admin [removes a member](https://omnigent.ai/docs/collaborate/auth#removing-a-member), all of that user's tasks are marked deleted. A username that is later re-registered does not see or inherit the old account's tasks. --- ## https://omnigent.ai/docs/build/tools # MCP & Tools Tools give your agent capabilities beyond conversation. They are declared under the `tools` key in your agent YAML, each identified by a name you choose. Omnigent supports MCP servers, built-in tools, Python functions, sub-agents, and inherited tools. ## MCP servers MCP (Model Context Protocol) servers expose tools over a standard protocol. On a shared session, the servers an uploaded agent declares can also be managed from the web UI's **Agent info** panel — but only by the session's owner. Edit collaborators see the list read-only. See [Only the owner can change the agent](https://omnigent.ai/docs/collaborate#owner-only-agent-changes). On a server that publishes an administrator-managed MCP registry, you can also turn on server-configured services for a single session from the new-chat composer's **MCPs** control, without declaring them in the agent YAML; credentials stay on the server. See [Choose MCP services before launch](https://omnigent.ai/docs/interact/web-ui#choose-mcp-services-before-launch). ### Custom servers For MCP servers not in the bundled set, declare them in your agent YAML. Transport is inferred: use `command` for local stdio servers, `url` for remote HTTP/SSE servers. **Local command (stdio):** ``` tools: my-server: type: mcp command: node args: [dist/server.js] env: API_KEY: ${MY_API_KEY} # env vars expanded at runtime ``` **Remote URL (HTTP/SSE):** ``` tools: docs-api: type: mcp url: https://example.com/mcp headers: Authorization: "Bearer ${API_TOKEN}" # env vars expanded at runtime ``` The optional `tools` list filters which MCP tools are exposed to the agent. It works the same on both transports — list the tool names you want to allow, and only those are surfaced to the model: ``` tools: github: type: mcp command: npx args: [-y, server-github] tools: [search_issues, get_pull_request] # only these two are exposed ``` Omit `tools` to expose everything the server provides. ### Sidecar MCP files Instead of declaring an MCP server inline under `tools` in `config.yaml`, you can keep it in its own file under the agent's `tools/mcp/` directory. Omnigent discovers every `tools/mcp/*.yaml` at load time. A sidecar file names the server and its transport explicitly (`transport: stdio` with `command`/`args`, or `transport: http` with `url`) and accepts the same optional `tools` allow-list as the inline form: ``` # tools/mcp/github.yaml name: github transport: stdio command: npx args: [-y, server-github] tools: [search_issues, get_pull_request] # only these two are exposed ``` ``` # tools/mcp/docs.yaml name: docs transport: http url: https://mcp.example.com/sse tools: [search_docs] ``` The `tools` allow-list is enforced identically whether the server is declared inline or in a sidecar file: only the listed tool names are advertised to the model, omitting `tools` exposes everything the server provides, and a `tools` value that is not a list is rejected at load time with an error naming the file. ### Authentication As shown in the examples above, custom MCP servers accept credentials through: - **`env`**: environment variables passed to stdio server processes (e.g., `API_KEY: ${MY_API_KEY}`) - **`headers`**: HTTP headers for remote servers, with `${...}` env var expansion (e.g., `Authorization: "Bearer ${API_TOKEN}"`) - **`auth:` with `type: databricks`**: resolves an OAuth token from a profile in your `~/.databrickscfg`. `databricks` is the only supported `auth` type; inside it, `profile` is required. Other servers don't use `auth:` — they authenticate via `env` or `headers` above: ``` tools: internal-api: type: mcp url: https://my-workspace.databricks.com/mcp auth: type: databricks profile: my-profile ``` ## Built-in tools Omnigent ships a set of built-in tools you can enable by name under `tools.builtins`, without writing any code. ### `web_search` A unified web search tool whose backend is determined by your agent spec: - **OpenAI models** — passes through to OpenAI's native `web_search_preview` (server-side, using the LLM API key). No `search_provider` needed. - **Other models** (Anthropic, Llama, Databricks-hosted, etc.) — set `search_provider` to name the engine explicitly. There is no default and no env var fallback, so the engine that runs is always explicit; with no `search_provider` set, the tool returns an error naming the options. Supported backends: | `search_provider` | API key | Notes | | --- | --- | --- | | `duckduckgo` | Keyless | No `api_key` required | | `keenable` | Optional | Keyless by default; supply `api_key` to lift rate limits | | `google` | Required | Also requires `engine_id` | | `perplexity` | Required | | | `nimble` | Required | | | `tavily` | Required | | Keyless backends run out of the box; keyed backends need credentials for a sturdier, higher-rate service. ``` tools: builtins: - name: web_search search_provider: duckduckgo # a keyless backend, as an example # api_key: ${PERPLEXITY_API_KEY} # required for keyed backends ``` #### Keenable Keenable is keyless by default. With no `api_key` in the spec it calls the keyless public endpoint and runs out of the box; supplying an `api_key` switches to the authenticated endpoint and lifts rate limits. Set the optional `max_results` to control how many results are returned (1–20, default 5): ``` tools: builtins: - name: web_search search_provider: keenable # api_key is optional — omit it to use the keyless free tier: # api_key: ${KEENABLE_API_KEY} # max_results: 5 # 1-20 (default 5) ``` ### Hindsight long-term memory Three built-in tools give an agent memory that survives across runs and sessions, backed by [Hindsight](https://github.com/vectorize-io/hindsight), an open-source agent-memory system. Unlike conversation history — which is lost between sessions — anything an agent retains stays available in every future run. | Tool | What it does | | --- | --- | | `hindsight_retain` | Persist a durable fact, preference, or decision to memory. Takes a `content` string. | | `hindsight_recall` | Search memory for relevant facts before answering. Takes a `query` string. | | `hindsight_reflect` | Synthesize a reasoned summary from memory, rather than returning raw facts. Takes a `query` string. | The Hindsight client SDK is an optional dependency. Install the `memory` extra and provide an API key: ``` pip install 'omnigent[memory]' export HINDSIGHT_API_KEY=hsk_... # https://ui.hindsight.vectorize.io ``` Enable the tools by name under `tools.builtins`: ``` tools: builtins: - name: hindsight_recall api_key: ${HINDSIGHT_API_KEY} - name: hindsight_retain api_key: ${HINDSIGHT_API_KEY} - name: hindsight_reflect api_key: ${HINDSIGHT_API_KEY} ``` #### Config keys `api_key` is required; every other key is optional: | Key | Purpose | | --- | --- | | `api_key` | Hindsight API key (typically `${HINDSIGHT_API_KEY}`). | | `api_url` | API base URL. Defaults to `https://api.hindsight.vectorize.io`. | | `bank_id` | Memory bank to read/write. Defaults to the agent id, then the conversation id. | | `budget` | Recall/reflect budget level — `low`, `mid` (default), or `high`. | | `max_tokens` | Max tokens for recall results (default `4096`). | | `tags` | Comma-separated tags applied on `hindsight_retain`. | | `recall_tags` | Comma-separated tags to filter `hindsight_recall`. | | `recall_tags_match` | How `recall_tags` are matched — `any` (default), `all`, `any_strict`, or `all_strict`. | Memory is scoped by the resolved bank. With no `bank_id`, the bank defaults to the agent id, so each agent gets an isolated memory bank out of the box. Pin a fixed `bank_id` to share one human-readable bank across runs (or several agents): ``` tools: builtins: - name: hindsight_retain api_key: ${HINDSIGHT_API_KEY} bank_id: remy # stable, easy to inspect in Hindsight ``` Because these tools only persist and recall when the model actually calls them, prompt the agent to recall before answering and to retain durable facts as it learns them — see the `remy` example agent for a worked prompt. ### Nimble web research and extraction Two builtins reach the live web through [Nimble](https://www.nimbleway.com/). `nimble_research` hands a research task to a Nimble Web Search Agent and returns a cited answer with a confidence signal for each claim. Use `nimble_extract` when you have a Nimble extract template to run against a page; it returns the parsed result as structured JSON. `nimble_research` needs the optional `nimble` extra: ``` pip install 'omnigent[nimble]' ``` Enable them in your agent's `config.yaml`, under `tools.builtins`: ``` tools: builtins: - name: nimble_research api_key: ${NIMBLE_API_KEY} - name: nimble_extract api_key: ${NIMBLE_API_KEY} template: my_template_name # one of your account's extract templates ``` Nimble is also a [`web_search`](#websearch) provider for quick lookups that don't need a research run. For the full config reference (polling, agent instances, extract templates, and where to get an API key), see [Nimble's Omnigent connector docs](https://docs.nimbleway.com/integrations/connectors/omnigent). ## Python function tools Expose any Python callable as a tool. The function is referenced by its fully qualified import path: ``` tools: summarize_file: type: function description: Summarize a local text file. callable: my_package.tools.summarize_file parameters: type: object properties: path: type: string required: [path] ``` The JSON Schema under `parameters` is optional. If you omit it, Omnigent auto-generates the schema from the function's type annotations and signature. Python function tools run in the server process, so they only work for agents the server operator registers. Agent bundles uploaded over HTTP cannot use them — see [Uploaded agents](#uploaded-agents). ### Uploaded agents Agent bundles uploaded over HTTP — to create a session, replace a session's agent (`PUT /v1/sessions/{id}/agent`), or install an agent — are validated before any of their code is imported. An uploaded bundle cannot declare: - Server-side Python or TypeScript source-file tools (for example, files under `tools/python/`). - Server-side callable tools (`type: function` with a `callable`). - `type: function` policies whose `handler` is not a [registered policy handler](https://omnigent.ai/docs/policies/custom#2-register-on-the-server). - Absolute working directories, or working directories that escape the workspace. These checks also apply to terminals and nested agents in the bundle. They run on multi-user servers and, starting in 0.19.0, on local single-user servers too, including bundles that `omnigent run` uploads. Loopback requests can come from agents as well as the operator, so a local server doesn't treat them as trusted. An invalid upload is rejected with HTTP 400. Existing uploaded agents that use these features also fail validation when you add, update, or delete their MCP server settings. A rejected edit leaves the stored bundle unchanged. The agent configuration read and patch endpoints apply the same validation to older bundles. To migrate an existing uploaded agent: 1. Expose its custom tools through an [MCP server](#mcp-servers) and replace the source-file or callable tool entries with `type: mcp` entries. Update nested agents too. 2. [Register custom policy handlers on the server](https://omnigent.ai/docs/policies/custom#2-register-on-the-server), and change working directories to relative paths inside the workspace. 3. Replace the session's bundle through `PUT /v1/sessions/{id}/agent`, or upload the corrected bundle and start a new session. Installing a new agent version on its own does not change existing sessions. 4. Make later MCP configuration changes on the migrated agent. Removing an MCP server from an old agent does not get it past validation. For trusted in-process tools, register the agent as the operator instead of uploading it: ``` omnigent server --agent ./my-agent # operator-registered agent omnigent run ./my-agent --no-session # dedicated local server, trusted startup registration ``` `omnigent run ./my-agent --no-session` starts a dedicated local server that registers the agent at startup and creates its session from that registration, so nothing is uploaded over HTTP. Changing a tool's declared runtime alone is not a migration: client tools need a client-provided implementation. ## Sub-agent tools Declare agents as tools so a supervisor agent can delegate work to them. You can define a sub-agent inline, or keep it in its own directory and reference it by name. ### Inline definition Define the sub-agent's full spec directly in the `tools` block: ``` tools: reviewer: type: agent description: Review proposed code changes. prompt: | You are a careful code reviewer. Focus on correctness, tests, security, and maintainability. executor: harness: claude-sdk model: claude-sonnet-4-6 os_env: inherit pass_history: true max_sessions: 2 ``` ### External directory For a sub-agent that is complex, shared across parents, or maintained independently, give it its own directory under the parent's `agents/` folder and list it by name under `tools.agents`. Omnigent discovers every `agents//config.yaml` at load time: ``` # Parent config.yaml tools: agents: - reviewer # agents/reviewer/config.yaml — a standard agent config spec_version: 1 name: reviewer description: Review proposed code changes. prompt: | You are a careful code reviewer. Focus on correctness, tests, security, and maintainability. executor: type: omnigent config: harness: claude-sdk model: claude-sonnet-4-6 ``` Entries under `tools.agents` are just names (strings) matching a subdirectory of `agents/`. Each referenced file is a full agent config that can define its own tools, skills, harness, and policies. A sub-agent (inline or external) can have its own [harness](https://omnigent.ai/docs/build/harnesses), model, tools, and [policies](https://omnigent.ai/docs/policies/overview). On an inline sub-agent the `pass_history` flag controls whether the parent's conversation history is forwarded, and `max_sessions` limits concurrent invocations. ## Tool inheritance Use `inherit` to pass one of the parent's tools down to a sub-agent: ``` tools: researcher: type: agent prompt: Research and summarize. tools: word_count: inherit # gets word_count from parent ``` The sub-agent receives the same tool definition the parent has, with no duplication. Use `spec: self` for a sub-agent that clones the entire parent spec. It gets the same tools, prompt, and configuration as the parent agent. ## Hindsight integration Omnigent ships [Hindsight](https://github.com/vectorize-io/hindsight) integration that gives an agent long-term memory. Three tools are available: - `hindsight_retain` — persist a durable fact across runs. - `hindsight_recall` — retrieve relevant memories before answering. - `hindsight_reflect` — reason over everything stored in the bank. These tools ship in an optional extra. Install it and set your Hindsight API key before declaring them: ``` pip install 'omnigent[hindsight]' export HINDSIGHT_API_KEY=hsk_... # https://ui.hindsight.vectorize.io ``` Declare them by their reserved names under `builtins`, passing the API key and an optional `bank_id` (omit `bank_id` and the memory bank defaults to the agent id): ``` tools: builtins: - name: hindsight_recall api_key: ${HINDSIGHT_API_KEY} bank_id: my-agent - name: hindsight_retain api_key: ${HINDSIGHT_API_KEY} bank_id: my-agent - name: hindsight_reflect api_key: ${HINDSIGHT_API_KEY} bank_id: my-agent ``` ## Combine tool types A single agent can mix all tool types. The `tools` block is a flat map of names to definitions: ``` tools: github: type: mcp command: uv args: [run, python, -m, my_package.github_mcp] summarize_file: type: function callable: my_package.tools.summarize_file reviewer: type: agent config: agents/reviewer.yaml ``` --- ## https://omnigent.ai/docs/interact/desktop # Desktop App The Omnigent desktop app gives you the full [web UI](https://omnigent.ai/docs/interact/web-ui) in a native window, plus OS-level features a browser can't provide: notifications when your agent needs attention, a dock badge showing how many sessions are waiting, and multiple independent windows. [Download for Apple silicon](https://omnigent.ai/download/mac) · [Download for Intel](https://omnigent.ai/download/mac-x64) ![Omnigent native desktop app showing the session chat and file workspace](https://omnigent.ai/images/docs/desktop-app.png) ## Why use it over the browser? - **OS notifications.** Get notified when an agent finishes, asks for input, or a runner disconnects. Notifications show the first few lines of the agent's message and only fire for sessions you're not actively viewing. - **Dock badge.** See at a glance how many sessions need your attention, without switching to the app. - **Multiple windows.** Open additional windows with `Cmd+N` to watch multiple sessions or servers side by side. - **Recent-session switcher.** Hold `Ctrl` and press `Tab` to flip between your five most recent sessions — see [Keyboard shortcuts](#keyboard-shortcuts). - **One-click host reconnect.** When a session's host is this machine and it has gone offline, the app restarts the host for you instead of asking you to run a command in a terminal. Everything else (sessions, file editor, inline comments, collaboration) works exactly the same as the web UI. ## Embedded browser The desktop app includes an embedded browser pane your agent can drive directly. Five framework-owned tools are always available to every agent — you don't declare them in your agent YAML: **Tool:** `browser_navigate` **What it does** Opens or navigates the pane to a URL, auto-opening the pane if it isn't open yet. **Tool:** `browser_snapshot` **What it does** Captures an accessibility-tree snapshot: a `snapshot_id`, the current URL and title, and a YAML-shaped tree of interactive elements, each tagged with a `[ref=N]` id. **Tool:** `browser_click` **What it does** Clicks an element by `ref` (preferred) or CSS `selector`. Pass the `snapshot_id` alongside a `ref` so a stale snapshot is rejected with a precise error. **Tool:** `browser_type` **What it does** Focuses an input and types `text` into it, identified by `ref` or `selector`. **Tool:** `browser_screenshot` **What it does** Captures a PNG of the pane for visual inspection. Prefer `browser_snapshot` when picking elements to act on — screenshots carry no refs. Refs from a snapshot are far more stable than CSS selectors against generated class names and Shadow DOM, so the usual flow is: `browser_navigate`, then `browser_snapshot` to see what's on the page, then `browser_click` / `browser_type` using the refs it returned. Because the browser runs inside the desktop window, these tools require the Omnigent desktop app to be running with the session open. If no desktop window is subscribed, the action fails cleanly with `browser action timed out — is the session open in the Omnigent desktop app?` rather than hanging. ### Local network permission Sites in the embedded pane can ask for **local network access** — for example a sign-in page that talks to a loopback helper such as Okta Verify. When they do, a compact Chrome-style popover appears beneath the browser toolbar, asking **Allow local network access?** for the requesting origin, with three choices: - **Allow once** — grants access for this site visit in this pane. It survives reloads and same-origin navigation, and expires when you leave the site or close the pane. - **Always allow** — remembers the decision for this exact origin across all conversations and app restarts. - **Deny** — blocks the permission for this origin across all conversation browsers and app restarts. Closing the popover, pressing Esc, or clicking outside it dismisses the request without saving a decision; the site isn't asked again until the next navigation. Only the visible, active pane can open a new prompt, but an existing **Allow once** or **Always allow** grant stays in effect while the site is in the background. HTTPS sites and HTTP loopback pages can request access; embedded subframes cannot request it on their own. Some sign-in flows check the permission before requesting it. Because Electron can only answer such checks with allowed or denied — never "prompt" — the popover then shows **Allowing will reload this page**, and either allow button reloads the page so the site retries with the new answer. If a background page has already given up on its sign-in flow, bring its pane into view and reload. Saved choices live in the desktop app's `settings.json` under `browser_local_network_permissions`, as a map of origin → `true`/`false`. To reset a saved choice, quit the app, remove that origin's entry, and reopen it. These choices don't change the pane's per-conversation cookie and storage isolation, and camera, microphone, notifications, and all other browser permissions remain denied. This controls Chromium's local-network permission answers — it is **not a network firewall**. Not every Electron version gates every local-network fetch on this permission, and approving it doesn't bypass CORS, TLS certificate validation, or the agent's navigation policy. A local helper that rejects the site through its own CORS policy must itself be configured to allow it; the pane doesn't rewrite those headers. ## Keyboard shortcuts The desktop app supports every [web UI shortcut](https://omnigent.ai/docs/interact/web-ui#keyboard-shortcuts) and adds two of its own. Both appear in the in-app shortcuts overlay (`⌘/` / `Ctrl+/`) only when you're running the desktop app. ### Switch recent sessions Hold **Control** and press **Tab** (`⌃Tab` on macOS, `Ctrl+Tab` on Windows/Linux — this chord uses Control, not ⌘, on every platform) to open a switcher listing your **five most recent sessions**, ordered by last activity, each with its agent name. While holding Control: - press `Tab` again to move down the list, or `Shift+Tab` to move up (the selection wraps); - release Control to switch to the highlighted session; - press `Esc` to cancel and stay where you are. The highlight starts on the session just after the one you're viewing, so a quick `Ctrl+Tab`-and-release jumps back to the session you used before this one. Archived sessions aren't listed. The gesture also works while an embedded [Browser](#embedded-browser) page has keyboard focus — the app forwards it to the switcher instead of letting the page handle it. ### Open a new browser tab Press `⌘⌥B` (`Ctrl+Alt+B` on Windows/Linux) to open a new **Browser** soft tab in the Workspace rail — the same action as **+ → Browser** in the rail's menu, which shows the keycaps next to the Browser row. If the rail is collapsed or hidden behind the Terminal panel, it's revealed with the new tab selected. The shortcut yields while you're typing in the code editor or a terminal, and is only bound where the embedded browser is available. `Cmd+N` still opens a new window; starting a new session is `⌘⌥N` (`Ctrl+Alt+N`). This differs from the browser web UI, which uses `⌘⇧O` (`Ctrl+Shift+O`); the shortcuts overlay and the sidebar's **New session** button show the chord for whichever app you're in. ## Prerequisites - **A running server.** The [server](https://omnigent.ai/docs/deploy/overview) stores your sessions and serves the UI the desktop app connects to; run it locally with `omni server start` or point the app at a deployed URL. - **At least one runner.** A [runner](https://omnigent.ai/docs/deploy/overview) is the process that actually executes your agent. If you have [cloud sandbox hosts](https://omnigent.ai/docs/deploy/cloud-sandbox-host) set up, you'll be able to launch them from the app. Otherwise, register your machine by running `omni host` from your CLI. ## Install **Platform:** macOS **Format:** `.dmg` **Status** Download for Apple silicon · Download for Intel **Platform:** Linux **Format:** `.AppImage` or `.deb` **Status** Download .deb · Download .AppImage **Platform:** Windows **Format:** Installer (`.exe`) **Status:** Download ## Get started ![Omnigent desktop app first-launch setup screen](https://omnigent.ai/images/docs/desktop-start.png) 1. **Launch the app.** On first launch, you'll see a setup screen. 2. **Enter your server URL.** `http://localhost:6767` if running locally, or your deployed server URL (e.g. `https://your-app.onrender.com`). 3. **Start working.** The app connects and shows the same UI you'd see in a browser. ![Omnigent desktop app showing an agent conversation alongside the session workspace](https://omnigent.ai/images/docs/desktop-conversation.png) The app remembers your server URL for future launches. To change it later, go to **Server > Change Server**. ## Work with multiple servers Each window connects to one server. To work with multiple servers at the same time: 1. Go to **Server > New Window on Different Server** 2. Enter the URL of the second server Notifications and badge counts are combined across all connected servers. ![Omnigent desktop app setup screen prompting for the server URL to connect to](https://omnigent.ai/images/docs/desktop-connect.png) ## Preset server URLs via MDM (macOS) On macOS, an administrator can preset the servers Omnigent Desktop offers, so users pick their organization's server from a list instead of typing it. This uses the standard macOS [Managed Preferences](https://support.apple.com/guide/deployment/welcome/web) mechanism, delivered through whatever MDM you run (for example Jamf, Kandji, Intune, or Mosyle). Each MDM has its own UI and configuration format, so follow your MDM's managed-preferences or custom-settings documentation for the exact steps. This page documents what Omnigent reads: the preference domain and the keys it recognizes. ### Preference domain Omnigent Desktop reads its managed preferences from the app's bundle identifier: ```text ai.omnigent.desktop ``` ### Keys **Key:** `serverUrls` **Type:** Array of strings **Required:** No **Default:** `[]` **Description** Server URLs to offer, most-preferred first. Each must use `https://`. At most 10. Give any entry a display name with an `omnigentServerName` query parameter (see Display names below). Notes: - A schemeless host is read as `https://`, and `http://` is rejected, so a plain `localhost` server can't be preset. - Paths are preserved, so you can point directly at a workspace mount such as `https://my-workspace.cloud.databricks.com/ml/omnigents`. - Entries that share an origin are collapsed, keeping the first. If any entry is an invalid type, insecure, or malformed, or if the list has more than 10 entries, the whole list is rejected. These preferences are read only on macOS; other platforms ignore them. ### Display names To give a server a display name, add an `omnigentServerName` query parameter to its `serverUrls` entry. Omnigent removes the parameter before connecting, so it never reaches the server: ```text https://my-workspace.cloud.databricks.com/?o=123&omnigentServerName=Engineering ``` Encode spaces as `%20`, and write `&` as `&` inside a plist. Unnamed servers show their host. The first preset server's name labels the connect screen's **Join your team** button; names also label the organization's servers in its dropdown and in the in-app server switcher. ### Example (macOS managed preferences) This is the macOS managed-preferences payload Omnigent reads. Depending on your MDM, you either upload it inside a Configuration Profile or paste these keys into a custom-settings form; either way, your MDM generates the surrounding profile wrapper (the `PayloadUUID`, `PayloadIdentifier`, and similar boilerplate). Keep the `ai.omnigent.desktop` domain and the key names as shown, and change the server URLs and names to your own. ```xml ai.omnigent.desktop Forced mcx_preference_settings serverUrls https://omnigent.corp.example.com/?omnigentServerName=Engineering https://my-workspace.cloud.databricks.com/ml/omnigents ``` ### What users see Preset servers show up under **Provided by your organization** on the connect screen and in the in-app server switcher. They are offered, not enforced: - Omnigent never connects to them automatically. - Users can still type and join another server URL. - A workspace path is loaded exactly as configured, rather than being reduced to its origin. - Values are read from macOS on demand, not copied into the app's settings, so applying or removing a profile takes effect the next time the connect screen loads or the server switcher opens. ## Open a session from a link The desktop app registers the `omnigent://` URL scheme, so a link can open a specific session on a specific server the way a web link opens a page. The link names the server by host and the session by its conversation id: ``` omnigent://localhost:6767/c/conv_abc omnigent://my-workspace.cloud.databricks.com/c/conv_xyz ``` The link carries no `http`/`https` — the app infers it the same way the setup screen does: `http` for a loopback host (`localhost`, `127.0.0.1`), `https` for a remote host. A Databricks workspace mount is discovered automatically, so you don't include it in the link. Only `/c/` links are recognized; other paths are ignored. Clicking a link behaves like opening a page for a known site: - If a window is already connected to that server, the app **reuses it** and navigates to the session in place. - If you've connected to that server before but have no window open for it, the app opens it in a **new window**. - If you've **never connected** to that server, the app asks you to confirm first, since connecting to a server grants it privileges like notifications and registering this machine as a runner. Cancel is the default; only open links from servers you trust. Once you allow it, the server is remembered so future links open without a prompt. --- ## https://omnigent.ai/docs/interact/mobile # Mobile The same session is accessible on terminal, browser, and phone simultaneously. Start on your laptop, continue on your phone. Everything syncs in real time across devices. You have three ways to use Omnigent on a phone: - **iOS app.** A native app from the App Store that connects to any Omnigent server you point it at. - **Android app.** A native app from Google Play that connects to your Omnigent server. - **Mobile web UI.** The full web UI in any phone browser if you prefer not to install an app. ![Omnigent running on a mobile browser](https://omnigent.ai/images/docs/mobile-demo.gif) ## The use case Long-running agents don't need you at your desk. Start a coding task, head to lunch, check progress from your phone. Your Omnigent hits a question. Answer it from the coffee line. Want to review what it produced? Browse the files right there. ## iOS app The Omnigent iOS app is available on the [App Store](https://apps.apple.com/us/app/omnigent/id6783102694). Like the [desktop app](https://omnigent.ai/docs/interact/desktop), it's a thin native shell around the server-served web UI, connecting to an Omnigent server you run or deploy rather than hosting sessions itself. ### Get started 1. **Install the app** from the [App Store](https://apps.apple.com/us/app/omnigent/id6783102694). 2. **Enter your server URL.** On first launch, the app asks for the URL of the Omnigent server to connect to. Enter your deployed server URL, or a Databricks workspace URL if you're using Omnigent on Databricks. The app remembers recent servers so you can switch between them. 3. **Start working.** The app connects and shows the same UI you'd see in a browser. ### Requirements - **A reachable server.** The app connects to an Omnigent [server](https://omnigent.ai/docs/deploy/overview); it doesn't run one on the device. The server must be reachable from your phone's network. - **HTTPS.** The app requires `https://` server URLs. A plain `http://` address (such as a bare `localhost`) is rejected, so a local dev server needs to be reachable over HTTPS or deployed. See [Deploy](https://omnigent.ai/docs/deploy/overview) for options. The app shows foreground notifications and updates the app badge while it's open. It does not do background polling, so notifications arrive when the app is in the foreground. ## Android app The Omnigent Android app is available on [Google Play](https://play.google.com/store/apps/details?id=ai.omnigent.android). It connects to a self-hosted or cloud-hosted Omnigent server and keeps your sessions in sync with the desktop and browser interfaces. ### Get started 1. **Install the app** from [Google Play](https://play.google.com/store/apps/details?id=ai.omnigent.android). 2. **Enter your server URL.** On first launch, connect to an Omnigent server you run or have access to. 3. **Start working.** Follow agent work, send messages, review outputs, and switch between sessions from your phone. ## Mobile web UI No install required. Open your Omnigent server's URL in any phone browser (Android or iOS) and you get the full web UI, touch-optimized: chat, monitor sub-agents, browse files, view code changes, and leave inline comments. `localhost:6767` is only reachable from the machine running the server. To reach it from your phone: **Same network.** Use your machine's local IP instead of localhost (e.g., `http://192.168.x.x:6767`). Your phone needs to be on the same Wi-Fi network. **From anywhere.** Deploy the Omnigent server beyond localhost. See [the deployment docs](https://omnigent.ai/docs/deploy/overview) for options. If you want a more app-like experience without the App Store, the web UI is an installable PWA: use your browser's **Add to Home Screen** option to launch Omnigent in its own standalone window. See [Install as an app](https://omnigent.ai/docs/interact/web-ui#install-as-an-app) for details. ## Managed configuration (org-preset servers) On the native **Android app**, an organization can preconfigure the server URLs users see, so people don't have to type one. The app publishes an [Android managed configuration](https://developer.android.com/work/managed-configurations) that any EMM/MDM console (Intune, Jamf, Workspace ONE, Google Workspace, the Android Management API) can push to enrolled devices. It has a single key: | Key | Type | Value | | ------------ | ------ | --------------------------------------------------------------- | | `serverUrls` | string | Server URLs, comma- or newline-separated, most preferred first. | For example, setting `serverUrls` to `https://omnigent.corp.example.com` offers that server at the top of the list. How the app treats presets: - **Offered, not forced.** Preset URLs are listed ahead of the user's recent servers on the connect screen and in the server switcher. The user still taps one to connect — that holds whether the policy sets one URL or several. - **Never auto-connects.** The app never connects to a preset on its own and never skips the connect screen, so a policy can't silently move someone onto a different server. - **Not a lock.** A user can still type any other server URL, and the one they pick stays current. - **Always live.** A preset is never written to the app's own storage, so an admin's later edit is picked up the next time the list is shown. - **Sanitized.** Unparseable entries are dropped, an entry without a scheme gets `https://`, same-origin duplicates collapse, and the list is capped at 8. The iOS app has no equivalent yet; when it lands it will reuse the same `serverUrls` key via Managed App Configuration. ## What works on mobile The full Web UI, touch-optimized: chat, monitor sub-agents, browse files, view code changes, and leave inline comments. ## Deep links On the native iOS app, an `omnigent://` link opens a specific session on a specific server directly in the app: ``` omnigent:///c/ ``` The link names a server by **host** (with a port if it isn't the default) and a conversation by its `/c/` route. It carries no `http`/`https`; the scheme is inferred the same way as the setup page: `http` for loopback hosts (`localhost`, `127.0.0.1`, `::1`) and `https` for any remote host, so a deep link and a pasted URL never disagree. For a Databricks workspace, the workspace mount (`/ml/omnigents`) is **not** part of the link; the app discovers it automatically after connecting. ``` omnigent://localhost:8000/c/conv_abc → http://localhost:8000/c/conv_abc omnigent://my-workspace.cloud.databricks.com/c/x → https://…/ml/omnigents/c/x ``` This first version accepts only conversation links (`/c/`). ### How a link is handled - **Already on that server.** The app navigates to the conversation in place, with no reload. - **A server you've connected to before** (your saved server or one in recents) that the app isn't currently on. The app switches to it and opens the conversation directly, with no prompt. - **A server you've never connected to.** The app asks you to confirm before opening. Connecting to a new server grants it privileges (notifications, app badge, microphone), so a clicked link never silently connects you to an untrusted server. Only open links from a server you trust. --- ## https://omnigent.ai/docs/interact/slack # Slack The Omnigent Slack integration lets your team talk to agents directly from Slack. Mention the bot in a channel or DM it, and it starts an Omnigent session; replies stream into the thread live, and tool approvals show up as interactive **Approve / Deny** cards you can answer in place. The core model is simple: **one Slack thread ↔ one Omnigent session**. Replying in a thread continues the same session; a new mention starts a new one. The bot connects to a **single Omnigent server chosen by the operator** — users never enter a server URL. Each user still authenticates as their **own Omnigent identity**, so sessions run with per-user permissions and no shared credential ever passes through Slack. ## What you can do - **Start a session** by mentioning the bot (`@omnigent help me inspect this failure`) or sending it a DM. - **Continue the conversation** by replying in the thread — it's the same session, with full context. - **Watch replies stream live** with Markdown rendering, in chronological order with any cards or notices. - **Approve or deny tool calls** with buttons, right in the thread (or from the web UI — whichever answers first wins). - **Answer agent questions** — multiple-choice prompts render as radio buttons or checkboxes with a Submit button. - **See the agent's plan**: the agent's todo list is posted once and edited in place as it progresses. - **Reconfigure any time** with `/omnigent` (pick agent, host, and workspace) or `/omnigent logout` to reset. ## Setting it up (operator) The bot is a **Socket Mode** Slack app — it opens an outbound WebSocket to Slack, so it needs no public HTTP endpoint and no request URLs. ### 1. Create the Slack app **Quick path — create the app from the manifest.** At [api.slack.com/apps](https://api.slack.com/apps) → **Create New App** → **From an app manifest**, pick the workspace, and paste `integrations/slack/deploy/slack-app-manifest.yaml` from the repo. That configures Socket Mode, Interactivity, every scope and event the bot needs, and the `/omnigent` command in one step. For Databricks web-auth, uncomment `users:read` and `users:read.email` in the manifest before pasting it. A manifest cannot mint tokens, so finish with the two steps that a manifest can't do — generate a `connections:write` app-level token (**Basic Information → App-Level Tokens**) and **Install to Workspace** for the bot token — then skip to step 2 below only if you need to double-check the scopes. Doing it by hand instead: 1. Create a Slack app with **Socket Mode** and **Interactivity** enabled (Socket Mode also delivers the button/modal payloads — no request URL needed). 2. Add the OAuth scopes and event subscriptions listed below. 3. Add a slash command `/omnigent` (Features → Slash Commands). In Socket Mode the request URL is ignored, so any placeholder works. 4. Install the app into your workspace. ### 2. Grant scopes These scopes and events are mirrored in `integrations/slack/deploy/slack-app-manifest.yaml` — if you created the app from the manifest they're already set, and if you change one, change both together. **Bot token scopes** (OAuth & Permissions → Bot Token Scopes) — all required: | Scope | Why | | ------------------- | -------------------------------------------------------------- | | `app_mentions:read` | Receive `@mention`s — how the bot joins a channel thread. | | `chat:write` | Post, delete, and stream replies (including ephemeral nudges). | | `im:write` | Open a DM to send the setup button and logout confirmation. | | `im:history` | Read DMs — DMs are a first-class entry point. | | `commands` | Register and receive `/omnigent`. | | `team:read` | Read the workspace name to label the login request. | Add two more bot scopes **only for Databricks web-auth mode**, where the bot signs the user's email into the enrollment link and matches it against the OAuth-authenticated identity; omit them in `accounts` / `oidc` mode: | Scope | Why | | ------------------ | --------------------------------------------------- | | `users:read` | Read the user's profile to look up their email. | | `users:read.email` | Read the user's email address for identity binding. | **Channel history scopes** — add only for the channel types where the bot will run: `channels:history` (public), `groups:history` (private), `mpim:history` (group DMs). If you only use DMs and channel `@mention`s, you can omit all three. **App-level token scope**: `connections:write` (required for the Socket Mode connection). **Event subscriptions** (Subscribe to bot events): `app_mention`, `message.im`, and `message.channels` / `message.groups` / `message.mpim` for the channel types you enabled above. ### 3. Install and configure the bot The bot ships as the `slack` extra of Omnigent (the separate `omnigent-slack` package), installed **in the same environment as** the `omni` CLI so `omni integration slack` can find it: ```bash uv tool install "omnigent[slack]" # or, from a source checkout: uv sync --extra slack ``` Configuration comes from **real environment variables** — the bot does **not** read a `.env` file itself (matching `omni server`). Export the variables directly, or launch the bot under a tool that injects a `.env` (e.g. `uv run --env-file .env omni integration slack`). `.env.example` documents the full set to copy from: | Variable | Required | Purpose | | ------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | | `OMNIGENT_SLACK_BOT_TOKEN` | Yes | The bot token (`xoxb-…`). | | `OMNIGENT_SLACK_APP_TOKEN` | Yes | The app-level token (`xapp-…`) for Socket Mode. | | `OMNIGENT_SERVER_URL` | Yes | The one Omnigent server the bot talks to. | | `OMNIGENT_DEVICE_CLIENT_SECRET` | No | Set to the same value as the server's `OMNIGENT_DEVICE_CLIENT_SECRET` so only this bot can drive the device-grant login. Accounts mode only. | | `OMNIGENT_SLACK_TOKEN_ENCRYPTION_KEY` | No | A Fernet key that encrypts users' delegated tokens at rest. Without it, tokens live in memory only and users re-authenticate after a restart. | | `OMNIGENT_SLACK_DATABASE_PATH` | No | Where the bot's SQLite store lives. Defaults to `$OMNIGENT_DATA_DIR/omnigent_slack.sqlite3` (or `~/.omnigent/omnigent_slack.sqlite3`). | | `OMNIGENT_SLACK_DEFAULT_AGENT_ID` | No | Pre-selects this agent id in every user's `/omnigent` setup modal. See [Operator-set setup defaults](#operator-set-setup-defaults). | | `OMNIGENT_SLACK_DEFAULT_HOST_TYPE` | No | Only `managed` — pre-selects the server-provisioned managed sandbox as the host in the setup modal. Any other non-blank value fails at startup. | | `LOG_LEVEL` | No | Set to `DEBUG` when diagnosing why Slack events aren't producing replies. | Generate an encryption key with: ```bash uv run python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" ``` If the Omnigent server runs as a **Databricks App** (header/proxy auth), the bot uses its own Databricks web-auth flow instead of the device/OIDC login — see [Databricks Apps web-auth](#databricks-apps-web-auth) below for the extra `OMNIGENT_SLACK_DATABRICKS_*` variables it needs. ### 4. Run it The bot runs as a background daemon managed by the `omni` CLI: ```bash omni integration slack # run in the foreground (Ctrl-C to stop) omni integration slack --background # run in the background (detached) omni integration slack status # is the background bot running? omni integration slack stop # stop the background bot omni integration slack logs # print the log path omni integration slack logs -f # follow the log ``` Running `--background` while the bot is already up is a no-op that reports the existing process. A foreground run refuses to start if a background daemon already holds the socket (stop it first). ## Per-user setup The first time someone interacts with the bot without having configured it, the bot DMs them a **Set up Omnigent** button. The button opens a modal that: 1. Validates connectivity to the operator's server, and — if the server has authentication enabled — walks the user through login (see below). 2. Lets them pick an **agent** and **host** (both required, populated from the server) and a **workspace path** — the absolute directory on the host where each session's runner starts. It defaults to the host's home directory. The choice is saved per (Slack workspace, user). Run `/omnigent` any time to change agent, host, or workspace, and `/omnigent logout` to revoke your token and clear all saved settings. Each new session launches a **fresh runner** on the chosen host, rooted at the configured workspace. ### Operator-set setup defaults If your team has standardized on one agent — or on the managed sandbox — the operator can pre-select those choices in step 2 for everyone, so a new user just submits the modal instead of making two decisions they have no opinion on: | Variable | Effect | | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | | `OMNIGENT_SLACK_DEFAULT_AGENT_ID` | Opens the **Agent** menu on this agent id — one of the ids the server returns from `GET /v1/agents`. | | `OMNIGENT_SLACK_DEFAULT_HOST_TYPE` | Only `managed` — opens the **Host** menu on the server-provisioned managed sandbox. Any other non-blank value fails at startup. | Both are set in the bot's environment like every other variable. Either may be left unset or blank for no default (the picker then opens blank, as it always has). These are **pre-selections, nothing more**. The modal still opens, still shows the full menus, and still has to be submitted — the user can change either choice, and what they submit is what is saved. Availability is checked per user, each time the modal is rendered, against the agents and hosts that user's own login can see. A default that isn't on offer — an agent id missing from their menu, or `managed` on a server that provisions no sandbox — leaves **that** menu blank and adds a warning note in the modal saying so. A different agent or host is never substituted, and a valid default in the other menu is still applied. The note distinguishes what it could establish from what it couldn't: if the server's sandbox capability couldn't be checked, the modal says the check didn't complete rather than claiming the server provisions none; and an agent past the menu's 100-option cap is reported as missing from the menu, not from the server. There is deliberately **no** default for a specific external host id: on an authenticated server `/v1/hosts` is owner-scoped, so one user's host is never listed for anyone else and could not be pre-selected. ## Authentication For servers with [authentication](https://omnigent.ai/docs/collaborate/auth) enabled, each Slack user logs in with their own Omnigent identity inside the `/omnigent` modal. The bot auto-detects the server's auth mode and picks the matching flow: - **`accounts` mode** — OAuth 2.0 Device Authorization Grant (RFC 8628). The modal shows a one-click verification link (code prefilled) and the short code to confirm; the user approves a consent page in their browser. The consent page **forces a fresh password entry** before approving — even if the user is already signed in — so a link the user didn't personally start can't be approved by reflex. The server issues a short-lived, session-scoped delegated token with a rotating refresh token. **The server must enable the device grant** (`OMNIGENT_DEVICE_GRANT_ENABLED=1` — it is off by default). Set the bot's `OMNIGENT_DEVICE_CLIENT_SECRET` to match the server's so only this bot can drive the flow. - **`oidc` mode** — the server's cli-login ticket flow. The user signs in at your IdP in their browser; the bot receives the server's regular session JWT. There is no refresh token, so after the session's TTL (default 8 hours) the user logs in again. - **`header` / proxy mode** — auto-detect can't drive a per-user login here, since identity is asserted by an upstream proxy. When the server is deployed as a **Databricks App**, set `OMNIGENT_SLACK_SERVER_AUTH=databricks` and the bot runs its own web-auth flow (see [below](#databricks-apps-web-auth)). Otherwise, run the server in `accounts` / `oidc` mode or place the bot behind the same identity proxy. Tokens are stored **encrypted at rest** when `OMNIGENT_SLACK_TOKEN_ENCRYPTION_KEY` is set; otherwise they're kept in memory only and lost on restart (users simply re-authenticate). ### Databricks Apps web-auth When the Omnigent server is a **Databricks App**, its proxy authenticates every request and injects the user's identity — a Socket-Mode event carries no such proxy-authenticated request, so the device/OIDC flows can't be driven. Instead the bot runs a **custom U2M OAuth app** (authorization code + PKCE, `offline_access`) through an enrollment page it serves as its own Databricks App: 1. On `/omnigent`, the bot looks up the user's email, generates a PKCE verifier and single-use nonce, and posts a **Sign in with Databricks** link to the workspace `/oidc/v1/authorize`. 2. The user signs in; Databricks redirects back to the bot's `/auth/callback` with a single-use, PKCE-bound code, which the bot exchanges for an access + refresh token pair. 3. **Identity binding** — the callback requires the OAuth-authenticated email to equal the Slack email signed into the link, so a link bound to one user can't store another's token. A mismatch is refused. 4. **Confirm before storing** — the callback shows a consent page naming the exact identities being linked; the token is persisted only when the user submits the confirming button, then the setup modal advances automatically. The bot presents the access token to the server; the proxy validates it and injects the real identity header, so **no server-side change is needed**. It refreshes silently, so the user signs in once, not hourly. Enable it with `OMNIGENT_SLACK_SERVER_AUTH=databricks` plus these variables: | Variable | Required | Purpose | | ----------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `OMNIGENT_SLACK_DATABRICKS_CLIENT_ID` | Yes | The custom U2M OAuth app's client id (public). | | `OMNIGENT_SLACK_DATABRICKS_CLIENT_SECRET` | Yes | The custom U2M OAuth app's client secret. | | `OMNIGENT_SLACK_DATABRICKS_STATE_SECRET` | Yes | HMAC key signing the enrollment `state` (≥32 chars of entropy, e.g. `openssl rand -hex 32`). Its own secret, separate from the OAuth client secret. | | `OMNIGENT_SLACK_DATABRICKS_APP_URL` | Yes¹ | Public base URL of the bot's own Databricks App — the enrollment link base and OAuth redirect base. Not platform-injected, and the URL only exists after the first deploy, so it's wired in on a second deploy pass. | | `OMNIGENT_SLACK_DATABRICKS_SCOPES` | No | Requested scopes (space-separated). Defaults to `all-apis`; must be a superset of the server app's scopes. `openid` + `offline_access` are always added. | ¹ Not enforced at startup — the bot boots without it, but no enrollment link is issued until it's set. The enrollment web server always binds `DATABRICKS_APP_PORT` (8000 by convention, platform-injected); there's no override knob. Register the custom OAuth app's redirect URI as `/auth/callback`. To deploy the bot as its own Databricks App, see `integrations/slack/deploy/databricks/README.md` in the repo. ## Approvals and questions When the agent needs you — a tool-call approval or a multiple-choice question — it appears in the thread: - **Tool approvals** render as an **Approve / Deny** card with a preview of the pending action. - **Multiple-choice questions** render as radio buttons or checkboxes with a **Submit** button. - **Free-form typed input** can't be collected with buttons, so the bot posts a link to the web UI instead — the turn stays alive and resumes once you answer there. Cards are visible to the whole channel, but **only the thread owner can act** on them. Answering from the web UI works too: the card in Slack finalizes automatically once the request is resolved anywhere. ## Sharing a channel with your team Slack channels are multi-user, so the bot enforces a **per-thread owner** model: a thread belongs to whoever started it. If someone else replies in your thread, they get a private note pointing them to start their own thread — they are never added to your session. If you message a thread while the bot is still replying, it privately asks you to wait (or continue in the web UI). A message to an idle thread just continues the conversation. ## Supported and not supported **Supported** - Channel `@mention`s, DMs, private channels, and group DMs (with the matching scopes) - Live streamed Markdown replies, in strict chronological order - Tool-approval cards and multiple-choice question forms, answerable in Slack or the web UI - Per-user identity on `accounts`- and `oidc`-mode servers, and unauthenticated servers - Per-user identity on `header`/proxy servers deployed as **Databricks Apps** (via the bot's web-auth flow) - Live-updating todo/plan messages, policy-denial notices, and produced-file notices - `/omnigent` (setup / reconfigure) and `/omnigent logout` slash commands **Not supported** - **`header`/proxy auth mode outside Databricks Apps** — auto-detect can't drive a per-user login; use `accounts`/`oidc` mode or place the bot behind the same identity proxy. - **Free-form typed input in Slack** — handled via a web-UI link instead of an in-thread form. - **Multiple servers** — the bot bridges to exactly one server, fixed by the operator. - **Multi-user threads** — a session belongs to the user who started the thread. - **Queueing messages mid-turn from Slack** — send again when the reply finishes, or continue in the web UI. ## Troubleshooting - **The bot doesn't respond to DMs** — make sure the `im:history` scope and the `message.im` event subscription are added, then reinstall the app. - **It can't reach your server** — the bot replies telling you to run `/omnigent` to reconfigure. - **No host is online** — the bot replies with the command to run on the machine you want to use: `omni host --server `, then run `/omnigent` again. - **Login never completes on an accounts-mode server** — check that the server sets `OMNIGENT_DEVICE_GRANT_ENABLED=1`. - **Events arrive but nothing happens** — set `LOG_LEVEL=DEBUG` in `.env` and check `omni integration slack logs -f`. --- ## https://omnigent.ai/docs/interact/terminal # Terminal The terminal is Omnigent's default interface: keyboard-first, with streaming output and real-time tool execution. If you already live in a terminal, this is the fastest way to work with your agent. Every interface (terminal, web UI, mobile, and desktop) connects to the same persistent, shareable session, so you can start work in one place and keep going anywhere. ![Omnigent terminal session](https://omnigent.ai/images/docs/terminal.png) ## Start a session The terminal interface requires `tmux`, so install it first if you don't have it: ``` # macOS brew install tmux # Debian / Ubuntu apt install tmux ``` Then launch an agent: ``` omni claude # Claude Code omni codex # Codex omni goose # Goose (native TUI) omni hermes # Hermes (native TUI) omni copilot # GitHub Copilot (SDK harness, via `omni run`) omni devin # Devin (native TUI) omni run agent.yaml # custom omnigent omni run ./my-agent/ # directory with config.yaml ``` `omni claude`, `omni codex`, `omni goose`, and `omni hermes` launch a built-in coding agent with Omnigent's UI. No YAML needed. `omni copilot` is shorthand for `omni run --harness copilot` and forwards every `omni run` option; it requires the Copilot SDK (`copilot` extra) and appears in `omni --help` only when that SDK is installed. See the [Coding Agent tutorial](https://omnigent.ai/quickstart/coding-agent) and the full harness list in [Harnesses](https://omnigent.ai/docs/build/harnesses). `omni run` starts a custom omnigent from a YAML config. See [Custom Agents](https://omnigent.ai/docs/use/custom-agents) for how to write one. A bare `omni` with no arguments does **not** launch an agent. On an interactive terminal it starts the local server / host in the background and returns; to launch an agent, name it (`omni polly`, `omni claude`, …) or use `omni run`. In a non-interactive shell (a pipe or CI, with no TTY), bare `omni` prints `--help` instead of blocking on a sign-in prompt. ### CLI overrides Override executor values at runtime without editing the YAML: ``` omni run agent.yaml --harness codex --model gpt-4.1-mini ``` The `--harness` and `--model` flags map directly to the `executor` block. This lets you reuse the same config across different LLM backends. From here, the agent streams its output as it thinks and acts. Tool calls run in real time, so you watch file writes, shell commands, and API calls land as they happen. The session also stays in sync with the [Web UI](https://omnigent.ai/docs/interact/web-ui) both ways: run the agent in your terminal and glance at the browser whenever you want file diffs or inline comments. ## Sessions and conversations A session is a conversation. Every message, tool call, and file change lives in one continuous thread. Sessions are the core unit of work in Omnigent, and three properties make them useful: - **Agent-independent.** A session belongs to you, not to a specific agent. Fork it at any point to continue on a copy (`omni run --fork `). To continue on a different agent, use **Clone session** in the web UI or pass `agent_id` to `POST /v1/sessions/{id}/fork`. A session's bound agent can't be changed in place. - **Persistent.** Every session has a conversation ID and lives beyond the current terminal window. Close your laptop, come back tomorrow, resume where you left off: `omni resume ` - **Shared.** Any omnigent can read the history of other sessions you have access to. When you spin up a new task, the agent already has context from previous work. ### Customize automatic session titles New sessions get an automatic title generated from your first message, so your session list stays readable without manual naming. You can steer the style or format of those titles with natural-language guidance in your Omnigent config: ``` omni config set --global \ 'session_title_instructions=Prefix titles with the current date as lowercase mon-dd. Use PR-number-short-name for pull requests, issue-number-short-description for issues, and a short snake_case activity otherwise.' ``` The isolated title generator receives the current date (as `YYYY-MM-DD`) and then applies your requirements to the first user message. Generated titles longer than 60 characters are rejected, leaving the first-message fallback title in place. Your requirements also apply to later agent-initiated renames through the `sys_session_rename` tool, not just the first-message title. When custom requirements are set, the agent's proposed title is reformatted to match them before the rename lands; if that formatting fails, the current title is kept. Without custom requirements, the agent's proposal is used as-is, and manual renames you make yourself are never reformatted. `session_title_instructions` is **server-owned** metadata configuration, not part of an agent's portable instructions, so it must be set with `--global`. Setting it as a project-local key (`.omnigent/config.yaml`) is rejected, and `omni config list` flags any project-local value as an ignored user-level-only setting. The setting takes effect after the local Omnigent server restarts, for both new-session titles and agent renames. For longer instructions, edit `~/.omnigent/config.yaml` directly and use a YAML block scalar: ```yaml session_title_instructions: | Prefix every title with the current date as lowercase mon-dd. For pull requests use mon-dd-PR-number-short-name. For issues use mon-dd-issue-number-short-description. For other work use mon-dd-short_snake_case_activity. ``` **Deployed servers.** On a shared server, an operator sets the same `session_title_instructions` key in the YAML passed to `omni server --config`; it then applies to every session on that server. Invalid values (non-string, or longer than 4,000 characters) are ignored and fall back to the default title prompt. ## When to use the terminal The terminal shines when you want to stay close to the command line: - **Fast iteration.** Keyboard-first, no context switching to a browser. - **SSH environments.** Works over SSH where a browser isn't available. - **Low-bandwidth connections.** Text-only, minimal overhead. - **Scripting and automation.** Composable with other CLI tools. --- ## https://omnigent.ai/docs/interact/web-ui # Web UI The web UI starts automatically with every session. When the server launches, the URL is printed in your terminal. Open it in any browser to get started. ![Omnigent web UI](https://omnigent.ai/images/docs/single-harness-demo.gif) ## Session management See all your sessions in one place. Resume any previous conversation, start new ones, switch between them. To continue a conversation on a different agent, use **Clone session** to fork it and pick the new agent for the fork; a session's bound agent can't be changed in place. Sessions persist across browser refreshes and server restarts. ### Session titles New sessions get an automatic title based on the first message. Rename a session at any time from its `⋯` menu or header menu. To control the format of automatic titles, configure [`session_title_instructions`](https://omnigent.ai/docs/reference/configuration#automatic-session-titles) on the server. To turn automatic titling off entirely, open **Settings → General → Sessions** and switch off **Automatically name new sessions**. When it's on (the default), Omnigent generates a concise title in the background after the first message of a new top-level session; turn it off to keep the default session name and rename sessions yourself. This is a per-user preference stored on the server, so it follows your account across browsers and devices. ### Export a session Save a conversation as a local file without leaving the browser: open the session's header menu and choose **Export**. The transcript downloads as `.jsonl` — one JSON record per line: - The first line is the session metadata (`record_type: "session_meta"`). - Every following line is one committed conversation item (`record_type: "item"`), in chronological order. Items keep their raw API shape, so messages, code blocks, and links come through as they were stored. This is the same format the `omnigent session export` CLI command produces, so a file exported from the web UI round-trips through `omnigent session import`. Long conversations are exported in full — the UI pages through the whole history before saving. If the export can't be built, an **Export failed** toast appears and nothing is downloaded. ### Hide unconfigured harnesses By default the new-chat picker lists every harness and badges the ones that aren't set up on the selected host (for example, a harness that still needs a CLI install or sign-in) so you can discover and configure them. If you'd rather see only the harnesses that can launch right now, turn on **Settings › Appearance › Hide unconfigured harnesses**. With it on, harnesses the selected host reports as unconfigured are hidden from the picker instead of badged — including the brain-harness override submenu of a bundle agent (though the currently selected brain always stays visible). This is a per-device UI preference, off by default. It changes only what the picker shows on this browser — no account or host state is touched — and fails open: when no host is connected or readiness information is unavailable, nothing is hidden. ### Projects Organize related sessions into a named `Project`. Each project is a collapsible folder in the sidebar, keeping a long session list grouped around the work you're doing. Projects don't nest, and a session belongs to at most one. Grouping is the only thing a project requires; the sessions inside it can each use whatever host, agent, and settings you like. Create a project from the sidebar at any time, including an empty one with no sessions yet, then move sessions into it as the work takes shape. You can file a session into a project from three places: - Start a new session already filed there: each project folder in the sidebar has a shortcut that opens the new-chat composer with the session assigned to that project. - Start a sibling session from inside a conversation: type **`/new`** in the composer (or type `/n`, press `Tab` to complete it, then `Enter`; selecting **/new** in the slash menu's **Commands** list works too). This opens a blank new-session composer filed into the same project as the session you're in. See [`/new`](#new-session-with-new) below. - Move an existing session: open the session's `⋯` menu and choose `Move session`, then search your projects, create a new one, or remove the session from its project. Removing a session from a project moves the session back to `Sessions` section, and deleting a project archives all its sessions. #### Reordering projects Projects start in **alphabetical order**. To arrange them yourself, drag a project's header up or down in the `Projects` list. A line shows where it will land when you drop it. Dragging a project switches the list to **manual order**. New projects appear at the end of that order. To change how projects are sorted, open the `Projects` section's `⋯` menu and choose **Sort projects by**: - **Alphabetically** — sorts projects A→Z while keeping your manual order available to switch back to. - **Manual order** — restores the order you last arranged. #### Project settings and defaults When the sessions in a project tend to run the same way, a project can store its own default session settings so you don't re-pick them every time. Open the project folder's `⋯` menu and choose `Project settings`, then set any of: - **Host** the new session runs on. - **Working directory** the agent starts in. - **Default agent** for the session. - **Default model**, shown only when the default agent is a native coding harness that takes a model choice (Claude Code or Codex). It pins the model new sessions start on — e.g. "Opus for this repo" — the same way the project pins an agent. The pre-filled model is still editable in the composer, and an explicit pick you make there wins over the project default. - **Random worktree**, which branches each new session into its own fresh git worktree. This overrides the global default in **Settings › Git** in either direction: turn it on to force worktrees for the project, or off to opt out even when the global default is on. Left unset, the project inherits the global default. New sessions started in that project pre-fill the composer from these defaults. Nothing is locked in: every field is still editable before you send, any default you leave unset falls back to your usual settings, and a default that no longer applies (an offline host, an uninstalled agent) is skipped rather than blocking the session. Leave the settings empty and the project stays a pure organizing tool. #### New session with `/new` When you're already in a conversation and want a fresh one alongside it, type `/new` in the composer and press `Enter`. The slash menu lists it under **Commands** as **Start a blank session in the same project**; `/n` followed by `Tab` completes it to `/new `, and clicking the row runs it directly. `/new` is handled by the web UI itself — it's local navigation, not a message. Nothing is sent to the agent, the current session keeps running untouched, and the composer is cleared. It takes no arguments: `/new something` shows **/new does not accept arguments** and does nothing else. Where it lands depends on the session you ran it from: - **Session in a project** — opens the new-session composer pre-filed into that project, so it picks up the project's [settings and defaults](#project-settings-and-defaults). The working directory follows the project's defaults, not the old session's. - **Session not in a project** (or whose project has since been deleted) — opens the plain, unfiled new-session composer. - **Project can't be looked up** (for example, the server is temporarily unavailable) — you stay in the current session and a toast reads **Couldn't open a new session in this project. Please try again.** Running `/new` again retries the lookup rather than reusing a stale project name. This is the same destination as `⌘⌥N` and the command palette's new-session action, with the current project carried over. ### Worktree branches In the new-chat composer, name a branch to run the agent in its own git worktree. Naming a branch reveals a **base branch** field, the branch the worktree is cut from. Leave it blank to branch off the workspace's current branch. To avoid retyping the same base, set a **Default base branch** under **Settings › Git**. The composer then auto-fills that value whenever you name a new worktree branch; leave the setting blank to turn auto-fill off. To always start new sessions in a fresh worktree, turn on **Always use a random worktree** under **Settings › Git**. When it's on, the composer seeds a fresh randomly-named worktree branch for any new session that lands in a git workspace, so you don't have to name a branch each time. It's off by default. A project's own **Random worktree** setting overrides this global default in either direction — a project can force worktrees on, or opt out even when the global default is on. Only projects that leave the setting unset inherit the global default. ![Setting a default base branch for worktrees](https://omnigent.ai/images/docs/git_worktree_setting.gif) ### Choose the launch model Native coding-agent sessions carry a per-session **Model** picker in their launch config, next to the harness's other launch knobs. Both Claude Code (`claude-native`) and Codex (`codex-native`) surface it: open the agent's config in the new-chat composer and pick the underlying LLM the harness launches with. The choices come from the **selected host's resolved model catalog**, so the list reflects the providers and credentials configured on that machine. The first option is **Default** — labeled `Default ()` when the host reports a default model — which leaves the harness on its own configured/default model. Leaving the picker on **Default** omits the override entirely. Your choice is remembered per harness, so a model picked for Codex doesn't change the model remembered for Claude Code (and vice versa). When you start the session, the selected model rides along as the launch override and the runner uses it at launch. Under a sandbox launch, where no host catalog is resolved, the picker is empty and the harness default is used. A project's stored **Default model** (see [Project settings and defaults](#project-settings-and-defaults)) outranks the remembered per-harness pick while the composer sits on the project's default agent, so a project can pin its own launch model. It only applies when the stored model is still a valid choice for that harness; a retired or unknown id falls back to your remembered pick. An explicit model you pick in the composer always wins for that session. ### Change reasoning effort mid-session Custom agents running on the `claude-sdk` or `codex` harness get an in-session **Effort** dial in the composer. Open the config gear, choose **Edit**, and pick a rung from the **Effort** section. The ladder is the harness's declared effort family, read from the server's harness catalog (`/v1/harnesses` → `capabilities.effort`): - `claude-sdk` (`anthropic` family): `low`, `medium`, `high`, `xhigh`, `max`. - `codex` (`openai` family): `none`, `minimal`, `low`, `medium`, `high`, `xhigh`. The pick is saved on the session's `reasoning_effort` right away — no turn has to be sent — and is restored when you reload the page. Changing effort after a completed turn applies on the next conversational turn and keeps the conversation history. On `claude-sdk`, effort is a launch option, so Omnigent rebuilds the SDK client with the history replayed; `/compact` still operates on the existing client's context. `/effort default` drops the override and restores the harness's default. On `codex`, restoring the default re-applies the thread's original effort; if the Codex app-server reports no default for the model, the turn fails with an error asking you to select an explicit effort or start a new session. This is the in-session dial, distinct from the effort choice in the new-chat composer. It appears only on top-level sessions (a sub-agent child takes no input of its own, so it gets no dial) and only for harnesses that declare an effort ladder — `antigravity` declares none, so its sessions show no dial. Native TUI sessions (`claude-native`, `codex-native`, …) keep their own effort controls. ### Set what the agent can do without asking Native coding-agent sessions choose their permission or approval mode from the composer's **permissions dropdown** — the hand icon next to the input — not from the picker's **Edit** menu. The Edit menu keeps the model and effort choices; **Advanced settings** now appears only for agents that have a configurable underlying harness (a bundle agent whose brain harness you can override). The options in the dropdown depend on the selected harness: - **Claude Code** (`claude-native`) offers its permission modes (for example **Accept edits** or **Bypass permissions**). The pick rides along as `--permission-mode`. - **Codex** (`codex-native`) offers its approval presets — **Default**, **Full access**, and **Read only** — plus the most-permissive **Bypass approvals & sandbox** option. Full access launches Codex with `--sandbox danger-full-access --ask-for-approval never`; the bypass option maps to Codex's `--dangerously-bypass-approvals-and-sandbox` stance and is create-time only (a running Codex session switches approvals through its own `/permissions` popup, which has no bypass row). - **Cursor** (`cursor-native`) offers its execution mode, such as **Plan**. - **Antigravity** (`antigravity-native`) offers **Ask every time** or **Skip permissions**. Skip permissions is a dangerous bypass that runs the session with all tool prompts disabled; while it's selected the composer shows a red warning banner. Under **Ask every time**, each Antigravity permission prompt appears as an approval card in Chat and in the Inbox, including agy's pattern-scoped always-allow choices — see [Antigravity permission prompts](https://omnigent.ai/docs/build/harnesses/configuration#permission-prompts). Your choice is remembered and seeds the next new session, the same way the model picker is remembered. ### Choose MCP services before launch On a server whose administrator publishes an MCP registry, the new-chat composer lets you pick server-configured MCP services for the session before you launch it. Open **MCPs** (the plug icon) in the workspace bar above the composer — the same bar as the working-directory and worktree chips — and the **Tools for this session** panel lists the catalog. Turn a service on or off with its switch; the chip reads `MCPs · N` while services are selected. When the bar runs out of room the label collapses and only the plug icon remains. Each row shows the service's connection state: - **Ready** — the service needs no account and can be used as-is. - **Connected** — your account is already linked. - **Needs sign-in** — turning the service on opens a sign-in popup; the service is selected only once authorization succeeds, and the **Send** button stays disabled (**Finish signing in to the MCP service…**) until then. Cancelling leaves it off. - **Connect your account in settings** — bearer-token services can't be turned on until you connect them under **Settings → MCP**, reachable from the **Manage MCP accounts** link at the bottom of the panel. - **A custom server already uses this name** — the agent's own MCP configuration already declares a server with this id, so the registry entry can't be added. Turning a service off removes it from this session only; the saved account stays connected. The selected ids ride along on session creation as `mcp_registry_services`, so the first turn can use the tools without a second attach or restart step. Selection is per session, not a host- or sandbox-wide default. **Remembered selections.** The composer remembers your explicit choices in this browser and applies them to the next new session — including choosing none. An unsent draft takes precedence over the remembered set. The preference applies across projects on the same browser origin, stores service ids only (never credentials), and a fresh browser starts with nothing selected. Existing sessions keep their own selection; changing the remembered set never touches them. Working-directory `.mcp.json` files are not read for automatic selection. When remembered selections are restored, launch waits for the catalog to load (**Loading MCP services…**). If the catalog can't be loaded, sending is blocked with **Could not load MCP services. Open MCPs to retry.**; open the panel and choose **Retry**. Ids that are no longer in the catalog are filtered out of the launch request without clearing your stored preference, and the panel lists them as **No longer available · turn off to remove**. A restored service whose connection has expired is called out in amber: the chip appends **· N needs sign-in**, and the row offers **Reconnect** — reconnect it or turn it off before launching. The **MCPs** control appears, and the catalog is requested, only when the server advertises the `mcp` connection capability; on other servers the composer shows no picker. ### Shared sessions On a multi-user server the sidebar splits your session list across two tabs: - **My sessions** — the sessions you own. This tab keeps the full sidebar structure: the `Pinned` section, `Project` folders, and the `Sessions` list. - **Shared with me** — a flat list of sessions other people have [shared with you](https://omnigent.ai/docs/collaborate). ### Browser-first mode If you prefer to work in the browser rather than the terminal UI, one command starts everything and returns: ``` omni host --background # start the local server + host daemon, then return ``` `--background` spawns the host daemon as a detached process instead of running it in the foreground. In local mode (no server URL) that daemon also owns the local Omnigent server, so this single command is the whole "start everything" step: it starts (or reuses) the local server, waits for it, and prints the server URL along with the daemon's pid and log path. It reuses a healthy daemon if one is already up rather than double-registering the machine. Open the printed URL (by default **http://localhost:6767**) and drive the agent entirely from the browser; no further terminal interaction needed. Stop the daemon later with the `omni host stop` command it prints. Sign-in still happens in the foreground, before the spawn: pointing `--background` at an authenticated remote server (for example `omni host --background --server https://your-server.databricksapps.com`) runs the browser login in your terminal first, since the detached daemon has no terminal to prompt on. ## Keyboard shortcuts The web UI ships a set of global keyboard shortcuts. Press `⌘/` (`Ctrl+/` on Windows/Linux) at any time to open the in-app shortcuts overlay, which lists the current bindings for your platform. Many controls also show their shortcut inline: hover the sidebar's collapse, **Search**, or **Settings** button or a Workspace tab, or open the Workspace rail's **+** menu, and the keycaps appear in the tooltip or menu row. ### Platform-aware modifiers Each global shortcut is bound to a single command modifier per platform, and only that modifier fires it: - On **macOS** the shortcuts use **⌘**, and only ⌘ triggers them. The Ctrl variant — and a combined ⌘+Ctrl chord — is inert, so Ctrl-based readline/emacs editing keys (`Ctrl+A`, `Ctrl+K`, …) keep working in the composer and terminal. - On **Windows/Linux** the shortcuts use **Ctrl**, and only Ctrl triggers them. Exceptions: `Ctrl+Shift+M` (model picker), `Ctrl+Shift+L` (focus chat input), and `Ctrl+Tab` (desktop recent sessions) use Control on every platform. What you see in the shortcuts overlay is what fires: the overlay shows ⌘ on macOS and Ctrl elsewhere. ### Switch sessions Move between sidebar sessions without leaving the composer: - **Previous session** — `⌘[` (`Ctrl+[`) - **Next session** — `⌘]` (`Ctrl+]`) The selection wraps at the ends of the list. The bracket chords carry no text-editing meaning, so they fire even while the composer is focused; inside a terminal or the code editor — which bind `⌘[` / `⌘]` themselves (indent/outdent, or forwarding to the PTY) — the shortcut yields to that surface. In the [desktop app](https://omnigent.ai/docs/interact/desktop#keyboard-shortcuts), `Ctrl+Tab` additionally opens a switcher over your five most recent sessions. ### Find a session by name Press `⌘⌥S` (`Ctrl+Alt+S` on Windows/Linux) to jump straight to the **Switch session** picker — a name-only search over your sessions. Type part of a session's title and the list fuzzy-matches it (for example, `fxprs` finds “Fix the parser”); press `Enter` to open the top match. The chord fires even while the composer is focused and leaves any unsent draft untouched when you `Escape` out; inside a terminal it yields to that surface. This picker lists sessions only — no commands or actions. It complements the command palette (`⌘K`), which searches sessions _and_ runs commands. The search loads more sessions as you narrow the query; when there are more matches than fit, a **Search older sessions** control pulls in older pages. ### Workspace sidebar and tabs `⌘⌥]` (`Ctrl+Alt+]` on Windows/Linux) is a keyboard path into the right **Workspace** rail rather than a plain toggle: - Pressed from anywhere outside the rail, it opens the rail if it's collapsed (or hidden behind the Terminal panel) and moves focus into the rail's tab strip, landing on the currently selected tab. If the rail is already open, it only moves focus there. Reopening brings back the file you last had selected. - Pressed while focus is already inside the tab strip, the same chord closes the rail — so two presses in a row open and then close it. Once the tab strip has focus, press a plain digit to jump to one of the [pinned](#pin-workspace-tabs) tabs: `1`–`4` select **Files**, **Changes**, **GitHub**, and **Agents** in the order they appear in the strip. Your [Default Workspace tab](#default-workspace-tab) comes first when it's available, and tabs that aren't available in the session (GitHub outside coding-agent sessions, Files/Changes without an on-disk workspace) or that you've unpinned are skipped so the numbers stay contiguous. Hover a tab to see its number in the tooltip. The digits only act while the tab strip is focused, so typing numbers in the composer, the editor, or a terminal is unaffected. ### Other global shortcuts | Action | Shortcut | | -------------------------------------------- | ---------------------------------------------- | | Open the command palette | `⌘K` (`Ctrl+K`) | | Find a session by name | `⌘⌥S` (`Ctrl+Alt+S`) | | New session | `⌘⇧O` (`Ctrl+Shift+O`) | | Open Settings | `⌘⌥,` (`Ctrl+Alt+,`) | | Open the model picker | `⌃⇧M` (`Ctrl+Shift+M` on every platform) | | Focus the chat input | `⌃⇧L` (`Ctrl+Shift+L` on every platform) | | Toggle the left (Conversations) sidebar | `⌘⌥[` (`Ctrl+Alt+[`) | | Focus or close the right (Workspace) sidebar | `⌘⌥]` (`Ctrl+Alt+]`) | | Select a Workspace tab | `⌘⌥]` then `1`–`4` (`Ctrl+Alt+]` then `1`–`4`) | | Open a new browser tab (desktop app only) | `⌘⌥B` (`Ctrl+Alt+B`) | | Open a new shell | `⌘⌥T` (`Ctrl+Alt+T`) | | Toggle voice dictation | `⌘⌥V` (`Ctrl+Alt+V`) | | Approve a pending request | `⌘↵` (`Ctrl+Enter`) | | Show the shortcuts overlay | `⌘/` (`Ctrl+/`) | `⌘⌥N` opens a clean, focused new-session composer from any page — the same destination as the command palette's new-session action. Inside a conversation, typing [`/new`](#new-session-with-new) in the composer does the same while keeping the new session in the current project. `⌘⌥,` jumps straight `⌘⇧O` (`Ctrl+Shift+O` on Windows/Linux) opens a clean, focused new-session composer from any page — the same destination as the command palette's new-session action. It fires even while the composer is focused, and any unsent draft in the session you left is kept. The chord matches the `O` character your keyboard layout types, so it follows the key labeled O on non-QWERTY layouts. The [desktop app](https://omnigent.ai/docs/interact/desktop#keyboard-shortcuts) keeps `⌘⌥N` (`Ctrl+Alt+N`) for this action instead. When the web UI is embedded in another page, the shortcut only fires while focus is inside the Omnigent app, so the host page keeps its own keys. `⌘⌥,` jumps straight to **Settings** from anywhere in the app. `⌘⌥B` is available only in the [desktop app](https://omnigent.ai/docs/interact/desktop#keyboard-shortcuts), where the embedded browser lives; the shortcuts overlay lists it only there. `Ctrl+Shift+M` is the keyboard equivalent of typing `/model` in the composer: it opens the model picker without leaving the keyboard. It works both in an existing session's composer and on the new-chat screen, where it opens the agent/model picker and drills straight into the selected harness's model submenu. The chord fires only when a model picker is available to open (it stays inert while the composer is read-only, the session is unreachable, or config is busy), and it yields to surfaces that own their own keys, such as the terminal and the code editor. Jump straight to a pinned session with the command modifier plus a digit (`1`–`9` for the first nine, `0` for the tenth). In a browser tab — where plain ⌘/Ctrl+digit is reserved for native tab-switching — the binding adds Alt (`⌘⌥1`…, `Ctrl+Alt+1`…); the installed desktop app uses the plain `⌘1`… chord. ## Canvas The Canvas page gives you a spatial view of your work: instead of the linear sidebar list, your top-level sessions appear as draggable cards you can arrange freely on an infinite canvas. Sessions are grouped by project, with one canvas per project, so each project keeps its own layout. ![Canvas showing sessions arranged as cards within a project](https://omnigent.ai/images/docs/canvas.png) Canvas is **off by default**. It's a gated release feature: an operator enables it deployment-wide by including `canvas` in the server's release features (`OMNIGENT_FEATURES=canvas`, or `--features canvas` on a Databricks deploy — combine comma-separated with other features like `usage_page,harness_install`; see [Release features](https://omnigent.ai/docs/deploy/overview#release-features)). Once enabled, a **Canvas** entry appears in the sidebar navigation and the page is served at `/canvas`. ## Install as an app The web UI is an installable [Progressive Web App (PWA)](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps). From a supported browser you can install Omnigent to your desktop or phone home screen and launch it in its own standalone window—no separate download or app store required. Look for your browser's install control (for example, the install icon in Chrome's address bar, or **Add to Home Screen** on mobile). Installing is optional: the web UI works exactly the same in a normal browser tab. Because Omnigent is a cloud app with no offline mode, the installed app still requires a connection to your server—it does not cache pages for offline use. ## Appearance Open **Settings → Appearance** to change how the web UI looks. Two controls sit side by side: - **Mode** — light or dark. - **Color theme** — a dropdown of palettes that restyle the whole UI: - **Omnigent** — the default brand look. - **Dracula** - **GitHub** - **Catppuccin** - **Gruvbox** - **Nord** — arctic frost blues over polar-night neutrals. Your mode and palette choices are remembered in the browser and reapplied on your next visit. Picking **Omnigent** clears any palette override and returns to the default look. ### Workspace panel default New chats start with the right **Workspace** rail **collapsed** by default, so a session stays visually stable while its runner and workspace hydrate. Expand it at any time from the header's **Expand right panel** control, or press `⌘⌥]` / `Ctrl+Alt+]`, which opens the rail and focuses its tab strip (see [Workspace sidebar and tabs](#workspace-sidebar-and-tabs)). To make new chats open the rail automatically instead, set the **Workspace panel** default in the **Workspace** group under **Settings → Appearance** to **Open**. The two choices are **Open** and **Collapsed**. This is a per-device browser preference that only seeds sessions with no saved open-state — once you expand or collapse the rail in a given session, that session keeps its own remembered state regardless of the default. ### Pin Workspace tabs The right **Workspace** rail's built-in pages — **Files**, **Changes**, **Pull Requests** (the [GitHub](#github) tab), and **Agents** — are all pinned to the tab strip by default. To declutter the strip, open the **Workspace** group under **Settings → Appearance** and use **Sidebar tabs** to toggle each page between **Pinned** and **Pin**. A **Sidebar preview** shows which icons the strip will display. Unpinned pages stay one click away in the rail's **`+`** (`Open new`) menu, listed above the browser, side-chat, and shell entries. Choosing one opens it as a closable tab next to your file, shell, and side-chat tabs; close it with its **×** button or a middle-click. Each session remembers which unpinned pages you opened. Sessions that had a page selected when you unpin it forget that selection and reopen on your default tab instead. If you unpin everything and haven't opened a page, the rail shows **Open a Workspace tab from the + menu.** Pinned tabs are a per-device browser preference, included in settings export and import. ### Default Workspace tab Choose which tab the right **Workspace** rail opens on first. In the **Workspace** group under **Settings → Appearance → Default Workspace tab**, pick one of your [pinned](#pin-workspace-tabs) pages: - **Files** — the default. - **Changes** - **GitHub** - **Agents** Only pinned pages are offered, and the control is hidden when no page is pinned. If your saved default is unpinned, the first pinned page is used instead. The tab you pick is shown first in the rail; the remaining tabs keep their relative order. Changing this default also updates existing chats when they're reopened or refreshed, without closing their open file tabs or changing the rail layout. Any manual tab choice you make in a session afterwards is remembered for that session and wins over the default, and file links still open the linked file regardless of the setting. This is a per-device UI preference stored in your browser. If a chosen default tab isn't available in a given session (for example, GitHub only appears in coding-agent sessions, and Files/Changes need an on-disk workspace), the rail falls back to the first available tab. ## File editor A full markdown editor with syntax highlighting for browsing and editing every file your Omnigent touches. Your Omnigent drafts a documentation page. You open it in the editor, read it properly formatted, and make direct edits. Your Omnigent generates a config file. You review it with full syntax highlighting. Use cases: updating documentation sites, reviewing Omnigent-generated plans, editing configs. ## Code viewer and diffs Review what your Omnigent changed before accepting. Syntax-highlighted diffs show exactly what was added, modified, or removed. ![Omnigent file editor](https://omnigent.ai/images/docs/file-editor.gif) ### Attach a selection to the agent In a coding-agent session, select lines in the code viewer or a diff and an `Attach to agent` button appears next to `Add comment`. Clicking it drops the selected line span into the composer as a chip (shown as `@path:start-end`), so you can ask the agent about an exact range without copy-pasting it into chat. The chip is delivered to the agent at send time as a path reference — the agent reads the on-disk file directly (no upload). This button shows only for native coding-agent sessions, since those harnesses run in the workspace and resolve the referenced file themselves. ## GitHub Coding-agent sessions have a **Pull Requests** tab in the right workspace rail. It tracks the pull requests associated with the session and shows the selected one's summary (reviewers, CI checks, and the PR description), its conversation comments, and a stacked diff with a file-tree navigator. Nothing is pushed or mutated — recording an association never touches the remote PR. ### Tracked pull requests Omnigent discovers a session's pull requests automatically by observing the agent's completed GitHub work — `gh` CLI commands and GitHub MCP tool calls that create, edit, review, merge, or comment on a PR. Each association is tagged with how it was found: - **created** — the session opened the PR. - **worked_on** — the session edited, reviewed, merged, or otherwise acted on it. - **attached** — you linked it manually (see below). - **inferred** — matched from the current branch when the session has no recorded PR yet; shown as `(from branch)` in the selector. Associations are stored on the host, independent of the current checkout, so they stay with the session even as its branch or worktree changes. When a session tracks more than one PR, a selector at the top of the tab switches between them; each entry reads `owner/repo #number` (prefixed with the host for GitHub Enterprise). The details, changed files, and diff below are always the selected PR's, fetched from GitHub — including fork PRs and renamed files — so they match the PR's "Files changed" exactly. When a file diff is computed from the local checkout instead, Omnigent diffs `HEAD` against the merge base with the base branch. In a shallow or single-branch clone (for example one created under an admin's [`sandbox.git_clone`](https://omnigent.ai/docs/reference/configuration/cloud-sandbox#git-clone-options) policy) a missing merge base or unfetched base branch produces an error naming the `git fetch` command to run, rather than a misleading branch-tip comparison. File content that cannot be read is also reported as an error, never shown as an added or deleted file. ### Summary tab The **Summary** tab opens with a **Reviewers** section, followed by CI checks and the PR description. Each assigned reviewer is listed with its current review state — **Pending**, **Approved**, **Commented**, **Changes requested**, or **Dismissed**. Team reviewers are marked `(team)`. A reviewer whose review has been re-requested shows as **Pending** even if they approved earlier. A PR with no reviewers reads `No reviewers assigned.` Reviewer data comes from the same `gh pr view` call as the rest of the PR. If GitHub refuses the reviewer fields for the active token (or the host's `gh` is too old to know them), Omnigent re-fetches the PR without them: the Reviewers section is simply omitted and the summary, checks, and comments still load. In the **Comments** tab, each author's avatar initial gets a distinct color within the thread so you can follow who said what at a glance. ### Link or unlink a PR Use the **+** (`Link a PR`) button to attach a pull request by URL — paste a `https://github.com/owner/repo/pull/123` link (GitHub Enterprise hosts work too). The linked PR becomes an `attached` association and is selected. The **trash** (`Unlink PR`) button removes the selected association; an unlinked PR stays out of the list until you link it again, even if the agent keeps touching it. When a session tracks no PRs yet, the tab's empty state offers the same `Link a PR` action. The composer status line mirrors this: it shows `#123` for a single tracked PR or `N PRs` when several are tracked, and clicking it opens the **Pull Requests** tab. ## Mentioning workspace files In a coding-agent session, type `@` in the composer to browse and attach workspace files and folders. Attached paths appear as chips above the input and can be removed individually; the agent reads each one directly from disk at send time. ![Tagging workspace files](https://omnigent.ai/images/docs/tag_workspace_files.gif) ## Appearance settings Open `Settings` → `Appearance` to tune how code renders. Separate from the chrome (UI) font, two controls size and style the **code editor (Monaco)** and the **terminal (xterm)** together: - **Code font size** — a stepper (`−` / value / `+`, or type a value directly). Ranges from **10** to **24** px and defaults to **13** px. - **Code font family** — a free-text field: type the name of any monospace font installed on your device. Leave it blank (or use `Reset`) for the editor default, which falls back to the app's shared mono stack. A custom name that isn't installed degrades to that mono stack rather than a serif fallback. Both settings apply live to any open editor or terminal — no reload or reconnect — and persist in your browser across refreshes and restarts. ## Inline comments and addressing Instead of typing in chat "change the third paragraph to be more concise," you click on that paragraph and leave a comment: "too verbose, tighten this up." The Omnigent sees exactly what you're referring to and revises it. **Addressing** closes the loop: resolve a comment and the Omnigent acts on the feedback. The workflow is: Omnigent proposes, you annotate, Omnigent revises. The comments panel (`Show comments` in the file viewer toolbar) sorts comments into two tabs, each with a count badge: - **Open** — comments you've left that haven't been sent to the Omnigent yet. - **Sent** — comments that have been dispatched to the Omnigent. Each card in this tab carries a `Sent` status label. (This tab was previously labelled **Addressed**; the underlying comment status is still `addressed`.) `Address All` sends every open comment to the Omnigent in a single message and moves them from **Open** to **Sent**. When a comment's status changes from elsewhere, its card moves tabs live — no reload needed. Where this matters most: - **Reviewing plans before execution.** Annotate the steps you want changed before the Omnigent starts building. - **Editing drafted docs.** Leave line-level feedback instead of rewriting in chat. - **Iterating on configs.** Flag the fields that need adjustment. ![An inline comment in the Open tab, with Open and Sent counts and the Address All button](https://omnigent.ai/images/docs/address-comment.png) ## Multi-modal input Paste images, screenshots, diagrams, and mockups directly into the conversation. The Omnigent sees them alongside your text. Useful for frontend work where describing a layout issue in words is slower than screenshotting it. ### Attach archives, Office documents, and databases Besides images, PDFs, and text/code files, the composer accepts ZIP archives (`.zip`), Office documents (`.docx`, `.xlsx`, `.pptx`), and SQLite databases (`.db`, `.sqlite`, `.sqlite3`). Drop them on the composer or pick them with the attach button — the file picker filter includes these extensions. They appear as ordinary file cards showing an icon, filename, type, and size; images keep their thumbnail and preview. These formats are classified by filename extension, not by the MIME type the browser reports, so a `.docx` that the browser labels as a ZIP still attaches correctly. Anything outside the supported set (audio, video, unrecognised binaries) is rejected with an inline error. **Harness requirement.** These files need a harness that can open local files: Claude Code (`claude-native`) or Codex (`codex-native`), running on an up-to-date host/runner. On other harnesses the upload is rejected; the server suggests attaching images, PDF, or text/code files instead, or switching to Claude Code or Codex. **Size limits.** The web composer caps each of these files at 50 MiB. Server operators can lower the per-file limit, cap the number and combined size of such files per session, or deny specific extensions — see [Attachment limits](https://omnigent.ai/docs/reference/configuration#attachment-limits). Images, PDFs, and text/code files keep their existing separate limits. ## Message queue and steering Type a follow-up while the agent is still working and it's held in a client-side queue instead of interrupting the turn. Queued messages appear in a docked strip above the composer, and drain automatically — one at a time, in order — as soon as the session goes idle. ![Steering and queuing a message](https://omnigent.ai/images/docs/steer-and-queue-msg.gif) Each queued row has inline actions: - **Steer** — send the message now, mid-turn, instead of waiting for the queue to flush. - **Edit** — pull the message back into the composer to revise it. - **Delete** — drop the message from the queue. ## Side chats A side chat lets you ask the agent something off to the side of a conversation — with the agent's full context — without adding anything to the main transcript. Side chats work on every harness. Each one opens as its own tab (`Side chat 1`, `Side chat 2`, …) in the right **Workspace** rail, beside the still-active main chat: you stay in the main conversation while the side chat streams next to it. The rail opens automatically when a side chat starts. Start one from any of these places: - Type **`/side `** in the composer. The `/side` row appears in the composer's slash menu on every harness; the menu lists each command and skill with its description inline. - Open the composer's **`+`** tray and choose **Start a new side chat** (under **Session**). - Open the Workspace rail's **`+`** (`Open new`) menu and choose **Side chat**. This opens an empty side-chat tab first; the side chat itself is created when you send its first message there. - Select text in a reply and choose **Ask in side chat** — the quoted selection and your question are sent to the side chat instead of the main conversation. Inside the tab you get a scoped composer (attachments and dictation included). Follow-ups go to the side chat, not the main conversation, and any approval request the agent raises there is answered there too. The side chat starts visually empty — the history it inherited from the parent is hidden — but the agent has the parent conversation's full context. ### Slash commands in a side chat The side-chat composer opens the same `/` menu as the main composer, with a narrower set of entries: **`/help`** plus the **session's skills**. Skills are discovered against the parent session's host and workspace, so the list matches what the main composer offers. Built-ins that act on the main session (`/model`, `/compact`, …) are not listed. - **`/help`** is handled locally in the side chat: it prints the available commands and their descriptions above the composer and is never sent to the agent as a turn. Choosing its menu row (or pressing Enter) runs it immediately; **Tab** only completes the text into the draft so you can edit it before sending. - **`/ [arguments]`** invokes that skill in the side chat through the same `slash_command` path as the main composer, rather than forwarding the text as plain prose. A slash-command receipt card records the invocation and stays visible after the skill's reply lands, even when the rest of the turn's tool work folds behind the **Worked** row. If skill discovery is still in flight when you submit a lone `/`, the submission is held until the catalog resolves instead of leaking to the agent as plaintext. - A multi-word skill whose first word collides with a built-in (for example `/help desk …`) still runs as the skill; a skill named exactly `help` can't be invoked because the built-in wins, so it is left out of the menu. The menu and local routing apply only to in-process harnesses. In a **native terminal** side chat (`claude-native`, `codex-native`) no menu opens and slash text is sent as-is to the vendor TUI, which reads it itself. A message carrying a quote or an attachment is always sent as an ordinary message, and the menu stays closed on an empty side-chat tab that hasn't created its session yet. ### How a side chat is created - **Codex** (`codex-native`) forks in-process using its native ephemeral `/side` fork, so the fork stays warm on Codex's prompt cache. Because that fork lives with the parent's runner process, a Codex side-chat tab restored after an app restart points at a thread that no longer exists: it opens read-only, showing its transcript with the notice **This side chat has ended and can't be continued.** - **Every other harness** forks the conversation server-side (a `POST /v1/sessions/{id}/fork` with `"side_chat": true`) and launches a runner for the fork on the **same host as the source session** — local or managed, with no new sandbox. When the source session is on a git branch, the fork runs in its own worktree on a `side-chat/` branch cut from the source branch, so it never touches the parent's working tree; otherwise it runs in the source's workspace. If the session has no host or workspace to launch on, or the fork fails, a toast reads **Couldn't start a side chat for this session.** and your typed question stays in the composer so you can retry. A side chat is otherwise a normal session with its own runner and transcript, but it is stamped with the `omnigent.side_chat` label and hidden from the left sidebar's session list — it surfaces only as a rail tab. ### Side-chat tabs Side-chat tabs are stored in your browser, like the rail's file and browser tabs: they survive reloads and restarts on this device but don't follow you to another device. Switching to another conversation shows that conversation's own side-chat tabs. Close a tab with its `✕` (or a middle-click) to stop the side chat's runner and drop the tab. Side chats are distinct from Claude Code's `/btw` overlay (below), which answers a one-off question inline without creating a session. ## Side chats with `/side` In a Codex (`codex-native`) session, type `/side ` in the composer to start a **side chat** — an ephemeral fork of the current Codex thread. The composer's slash menu offers `/side` only on Codex sessions; selecting it pre-fills `/side ` so you can type your question after it. Unlike `/btw`, a `/side` question is not answered in a throwaway overlay. It opens as its own **sub-agent chat**, labelled **Side chat**, so you can navigate into it and keep the conversation going over multiple turns. It never appears in the left conversation sidebar, and the main Codex conversation keeps running unchanged the whole time. The fork inherits the parent thread's history as **reference context only** — the side chat is told to treat that history as background, not as active instructions — so you can ask about the work so far without steering the main thread. The `/side` message itself stays out of the parent conversation: it's routed straight into the fork and leaves no bubble in the main transcript. Side chats are **ephemeral**: nothing is written to disk, so a side chat is not resumable after a runner restart. If a `/side` request can't be opened, the session posts a visible notice so the question is never silently dropped. You can also start a side chat from selected text. Select part of a message and choose **Ask in side chat**: the selection is added as a quote card, and when you send, it's forwarded as a `/side` command carrying the quoted text and your question. On a harness without side-chat support this action degrades to an ordinary quoted reply in the main chat. ## Side chats with `/side` Side chats work on every harness. A side chat forks the conversation into a new session titled **Side chat** and opens it as a tab in the right **Workspace** rail, so you can ask a tangent question with the full context of the parent conversation without adding anything to the parent's transcript. The parent stays on its own page and its history is untouched; the side chat's messages are saved only on the forked session. There are three ways to start one: - Type `/side ` in the composer and send it. The slash menu offers `/side` on every session. - Pick **Start a new side chat** from the composer's `+` tray. - In the rail, click **Open new** (`+`) and choose **Side chat**, then type your question in the side-chat pane. The side chat runs in the parent's **current working directory** — it shares the parent's workspace rather than cutting its own git worktree, and a branch saved on the parent (for example, from a worktree created on another machine) is neither required nor reused. Where it runs depends on how the parent is placed: - **Hosted sessions** (bound to a host and workspace) launch a separate runner for the fork on the parent's host, in the parent's workspace. - **CLI sessions** with an online runner but no host reuse the parent's existing runner. - **In-process sessions** with neither a host nor a runner use normal server dispatch. If the parent is on a dormant resumable managed host, starting a side chat wakes the host first and then launches on the refreshed placement. If neither the parent's host nor its runner is reachable, the side chat isn't created and the composer shows **This session is disconnected. Reconnect it before starting a side chat.** Closing a side-chat tab stops the forked session only; the parent keeps working and you can continue chatting in it. Codex native sessions handle `/side` natively (an in-process fork) instead of using this generic flow. ## Scheduled tasks Run an agent on a recurring schedule instead of starting each session by hand. A scheduled task is a saved prompt plus a recurrence rule: it fires automatically, runs the chosen agent, and keeps firing until you delete it. To create one manually, open the **New scheduled task** dialog and fill in: - **Name** — a label for the task (for example, `Nightly triage`). - **Prompt** — the instruction the agent receives on every run. - **Runs with** — the agent or harness to run. Scheduled tasks use that agent's default model, effort, and permission settings; there are no per-task model or effort overrides in this dialog. - **Frequency** — one of four presets: **Hourly**, **Daily**, **Weekdays**, or **Weekly**. Hourly fires once an hour and asks only for the **minute** of the hour; Daily and Weekdays ask for a **time**; Weekly adds a weekday multi-select (**On days**). Times are chosen in 15-minute steps. The schedule is evaluated in your browser's local timezone. - **Host (optional)** — pin the task to a specific connected host, or leave it set to **Resolve at fire time** to run on your connected host whenever the task fires. When the server has [managed sandboxes](https://omnigent.ai/docs/deploy/cloud-sandbox-host) configured, the picker also offers **New Sandbox** (labelled with the provider's name, e.g. **Modal Sandbox**): each run then provisions a fresh sandbox instead of using a connected host, and the sandbox's shutdown follows the server's sandbox configuration. Existing sandbox hosts can't be pinned. - **Workspace (optional)** — shown only once a connected host is pinned. Pick a directory to pin it; leave it unset to default to the host's home directory. A workspace can't be set without a host, and doesn't apply to sandbox runs. The **Create task** button stays disabled until Name, Prompt, a valid schedule, and an agent are all set. ## Collaboration Share your session with teammates for real-time co-driving. Multiple people see the same conversation, leave comments, and interact with the Omnigent simultaneously. See [Pair Programming](https://omnigent.ai/docs/collaborate) for details on sharing, permissions, and multi-user workflows. --- ## https://omnigent.ai/docs/collaborate # Pair Programming Omnigent is multi-user. Once your server is reachable by your team (see [Shared Server](https://omnigent.ai/docs/deploy/overview)), you can share a live session with a link, let a teammate co-drive your Omnigent, or fork a conversation so someone can continue independently. The same session works across [terminal](https://omnigent.ai/docs/interact/terminal), [web UI](https://omnigent.ai/docs/interact/web-ui), and [mobile](https://omnigent.ai/docs/interact/mobile) simultaneously. ## Co-drive A teammate attaches to your running session and their messages execute on your machine. Great for pairing or handing the keyboard to a domain expert mid-investigation. - In the [web UI](https://omnigent.ai/docs/interact/web-ui), hit **Share** and send the link to your teammate. - Share grants one of two permission levels: **Read** (read-only, teammates watch the session stream) or **Edit** (teammates can co-drive and send messages). Changing the session's agent — its MCP servers or the agent bundle itself — stays with the owner; see [Only the owner can change the agent](#owner-only-agent-changes) . - From the terminal, run: `omni attach ` Teammates see everything in real time: messages, tool calls, and file changes stream to every connected viewer. An admin can restrict or disable sharing server-wide (read-only, or off entirely), turn off public "anyone-with-the-link" access, or make new sessions start with public read access by default. If the **Share** control is missing or capped at read-only, or a new session is already public, that's the server's sharing policy — see [Session sharing](https://omnigent.ai/docs/collaborate/auth#session-sharing). ### Read-only sharing keeps workspace files private A **Read** (view-only) share gives a teammate the conversation — messages, tool calls, and streaming updates — but **not** the session's workspace files. By default, view-only viewers don't see the **Files**, **Changes**, or GitHub diff surfaces, because a workspace routinely holds secrets like `.env` files and keys. **Edit** collaborators always have workspace access. To let view-only viewers browse the files too, open the **Share** dialog and turn on **Workspace files** ("Let people with view access browse this session's files"). This toggle appears only for sessions that have a workspace on disk, and only the session owner can change it. It's off by default. Turning it off again returns view-only viewers to conversation-only access. The opt-in never widens absolute-path browsing on the host filesystem — that stays owner-only regardless of this setting. ### Co-driving grants code execution on the host Only share **Edit** with people you trust to run arbitrary code on your machine. A session runs commands in a non-sandboxed process on whatever machine hosts it, so a teammate with Edit can drive the agent and gets the same access to your filesystem, credentials, and network. To collaborate more safely, share read-only instead, or host the session on a [cloud sandbox host](https://omnigent.ai/docs/deploy/cloud-sandbox-host) so the agent runs in an isolated remote container rather than on your personal machine. To constrain what the agent can touch even on a trusted host, apply an [OS sandbox](https://omnigent.ai/docs/reference/configuration/os-sandbox) policy. ### What each permission level can do Everything an **Edit** collaborator does goes _through the agent_ — messages, workspace-relative file edits, and answering the session's pending prompts — so it passes the same policy and approval gates as the owner's own turns. A few actions bypass the agent entirely and stay with the session owner: | Action | Read | Edit | Owner | | -------------------------------------------------------- | :--: | :--: | :---: | | Watch the session stream | ✓ | ✓ | ✓ | | Send messages and drive the agent | | ✓ | ✓ | | Browse and edit files inside the workspace | \* | ✓ | ✓ | | See and answer pending prompts (approvals, questions) | | ✓ | ✓ | | Change a native session's permission mode or launch args | | | ✓ | | Transfer a live terminal to another session | | | ✓† | | Browse absolute paths on the host filesystem | | | ✓ | | Run a raw shell command in the session's environment | | | ✓ | \* Off by default; the owner can enable it with the **Workspace files** toggle described above. † The caller must own both sessions and the host the terminal runs on. The permission or approval mode of a native coding-agent session (for example Claude Code's permission mode or Codex's approval preset), and its terminal pass-through launch args, control what runs on the host without asking. Only the session owner can change them. The server enforces this on `PATCH /v1/sessions/{session_id}` (`permission_mode`, `approval_mode`, and `terminal_launch_args`). In the web UI, Edit collaborators see the composer's permissions dropdown disabled, with the hint "Only the session owner can change the permission mode". "Pending prompts" covers everything the agent pauses on: policy approval gates, harness permission prompts, questions the agent asks you, plan review, and MCP forms. The environment shell (`POST /v1/sessions/{session_id}/resources/environments/{environment_id}/shell`) is **owner-only** (a server admin may also use it). A command has no path to inspect, so there is no "inside the workspace" form that could safely be opened to collaborators — any command reaches the owner's machine directly and passes no policy or approval gate on the way. It carries the same bar as browsing an absolute path. Collaborators with **Edit** get a `403` from this endpoint; they can still ask the agent to run commands, which are subject to the session's policies. Pending prompts are answerable by anyone who can drive the agent (**Edit**), whether from the web UI's approval bar or the REST elicitation endpoints. A **Read** share can neither see nor answer them. ### Only the owner can change the agent Co-driving lets a teammate send messages, but it does not let them reconfigure the agent a session runs. Adding, editing, or removing the session's **MCP servers**, and replacing the session's agent bundle (`PUT /v1/sessions/{session_id}/agent`), require **owner** access to the session — **Edit** is not enough. On top of session ownership, the caller must also own the agent bound to the session (or be a workspace admin). Both checks are enforced by the server, so this applies to API clients as well as the UI. In the web UI, open the session's actions menu and choose **Agent info** to see the bound agent's tools and policies. Everyone with access to the session sees the MCP server names listed there; only the owner sees the **Manage MCP servers** control and the per-server edit and delete actions. Child sessions inherit their parent's access, so an owner keeps the controls in sub-agent sessions too, while a collaborator with Edit sees a read-only list. Built-in template agents and native coding-agent harnesses (`*-native`) never expose MCP editing here, regardless of who is looking. When auth is disabled or the server runs in local single-user mode there is no ownership model, and the MCP controls are always available for editable agents. ### Who said what When more than one authenticated person participates in a session, Omnigent labels each model-visible user message with the author's account, prefixed as `[account]:` (for example `[alice@example.com]: ...`), so the agent can tell collaborators apart mid-conversation. Labels appear only once two or more distinct authors have contributed; a solo session is left unlabeled. The label is **informational only**. It does not change stored message authors, UI avatars, session ownership, or who may approve or run privileged actions — authorization is unaffected. To hide the labels from the model, set the environment variable before starting the server: ``` OMNIGENT_SHARED_MESSAGE_ATTRIBUTION_ENABLED=0 omni server start ``` The switch is on by default; any of `0`, `false`, `no`, or `off` disables it. Disabling it removes the prompt labels only — internal authorship metadata is still stripped from what the model sees, and persisted authors are unchanged. ## Fork Clone a conversation and continue independently from the fork point. The original session is unaffected. In the [web UI](https://omnigent.ai/docs/interact/web-ui), click **Clone Session**. From the terminal: ``` omni run --fork ``` Forking copies the full conversation history up to the fork point. From there, each session diverges independently. ### Fork onto a cloud sandbox By default a fork is created **unbound**: it starts `idle` with no runner, and you attach compute afterwards the same way you would when resuming a session. If your server is configured with a [cloud sandbox provider](https://omnigent.ai/docs/deploy/cloud-sandbox-host), you can instead have the fork land directly on a fresh server-provisioned sandbox host. In the web UI **Clone Session** dialog, pick a **New Sandbox** target instead of an existing host. The server provisions the sandbox, starts the runner, and connects it back automatically — the same background launch a new sandbox session uses, so the clone appears before the sandbox has finished coming up. The sandbox is registered to you, the forking user, so it resolves your credentials rather than the source session owner's. The sandbox's working directory follows the source by default: leaving the workspace unset inherits the git repository the source session recorded, so cloning a sandbox session lands the fork in the same checkout. You can point the fork at a different repository (`https://github.com/org/repo#branch`), or choose an empty sandbox instead. See the [fork endpoint](https://omnigent.ai/reference) in the REST API reference for the `host_type`, `sandbox_provider`, and `workspace` request fields. --- ## https://omnigent.ai/docs/collaborate/auth # Auth & SSO Omnigent supports three ways to authenticate users. Pick the one that fits your setup: | Mode | When to use | | --------------------------------------------- | ------------------------------------------------------------------------ | | [Built-in accounts](#built-in-accounts) | Standalone deploy, no external IdP. Username/password with invite links. | | [Single sign-on (OIDC)](#single-sign-on-oidc) | Your own IdP: Google, GitHub, Okta, Microsoft. | | [Header-based auth](#header-based-auth) | Behind an existing SSO proxy that injects `X-Forwarded-Email`. | ## Built-in accounts If you deployed with [Docker](https://omnigent.ai/docs/deploy/overview#docker-compose) or a [cloud platform](https://omnigent.ai/docs/deploy/overview#cloud-platforms), auth is already enabled by default with built-in accounts. ### Non-Docker deploys For non-Docker deploys, enable built-in accounts manually: ``` OMNIGENT_AUTH_ENABLED=1 omni server start ``` 1. Create the first admin. - For better security, the server never auto-generates a password. When no admin is configured, the server will report `needs_setup`. - To configure, open the web UI and create an admin account or run `omni server` in a terminal and answer the username and password prompt. - For headless deploys, preset the password with `--admin-password` or `OMNIGENT_ACCOUNTS_INIT_ADMIN_PASSWORD`. 2. Invite teammates. - Go to **Admin > Members > Invite** to create a single-use invite link. - No email server needed; just send the link directly. Signup is invite-only. **Security note for public deployments.** While no password-bearing account exists, the Create-admin endpoint (`POST /auth/setup`) is intentionally unauthenticated, so an instance exposed on a public URL before its operator reaches the form can be claimed by the first visitor. Pre-seed `OMNIGENT_ACCOUNTS_INIT_ADMIN_PASSWORD` so the admin is created directly at boot, or keep the service private until you've completed setup. ## Single sign-on (OIDC) Let your team sign in with Google, GitHub, Okta, or Microsoft. Adding an OIDC issuer flips the mode to SSO. No extra flag needed. Set the following in `deploy/docker/.env`: ``` OMNIGENT_OIDC_ISSUER=https://accounts.google.com OMNIGENT_DOMAIN=agents.yourcompany.com OMNIGENT_OIDC_CLIENT_ID=... OMNIGENT_OIDC_CLIENT_SECRET=... ``` ``` docker compose up -d # restart to apply ``` The only outside step is creating an app with your provider (e.g. Google Cloud Console, or GitHub > Settings > Developer settings) to get the client ID and secret. Set its callback URL to `https:///auth/callback`. ### Custom email claim Omnigent reads the user's identity from the `email` claim of the IdP's `id_token` by default. Some providers omit that claim — Microsoft Entra ID commonly issues only `preferred_username` (the UPN) — which fails login with `Could not determine user email`. Point Omnigent at the claim that carries the identity instead: ``` OMNIGENT_OIDC_EMAIL_CLAIM=preferred_username OMNIGENT_OIDC_SKIP_EMAIL_VERIFICATION=1 ``` `email_verified` describes the `email` claim only, so a custom claim carries no verified marker. Logins are rejected unless you also set `OMNIGENT_OIDC_SKIP_EMAIL_VERIFICATION=1` to waive that gate — only enable this for an IdP you trust to vouch for the claim. When set, the claim value is trimmed and normalized like an email address. This applies to the generic-OIDC path only; GitHub always requires a verified primary email. ### Public PKCE clients and explicit endpoints The generic-OIDC path (Okta, Auth0, Keycloak, Entra ID, and any other IdP that publishes `/.well-known/openid-configuration`) accepts two further operator-level options. Both configure **Omnigent login** only — not upstream MCP credentials or shared-session credential ownership — and neither relaxes the signature, expiration, email-verification, admission, or reauthentication checks that run on every sign-in. #### Public clients (no client secret) If you registered Omnigent with your IdP as a **public client**, opt in with `OMNIGENT_OIDC_TOKEN_ENDPOINT_AUTH_METHOD=none` and leave `OMNIGENT_OIDC_CLIENT_SECRET` unset: ``` OMNIGENT_OIDC_ISSUER=https://identity.example.com OMNIGENT_OIDC_CLIENT_ID=omnigent OMNIGENT_OIDC_TOKEN_ENDPOINT_AUTH_METHOD=none ``` Omnigent still sends its client ID, redirect URI, and PKCE verifier to the token endpoint, but omits the `client_secret` field entirely. PKCE S256 is used in both modes, so the IdP must enforce PKCE and one-time authorization codes. This is an explicit opt-in. The default, `client_secret_post`, continues to require `OMNIGENT_OIDC_CLIENT_SECRET` — a missing secret never silently switches you to public-client mode. The server refuses to start if you set `none` together with a client secret, if you set any other value, or if you combine `none` with GitHub login (GitHub always needs the secret). If you are migrating from a custom integration that inferred public-client mode from an absent secret, add `OMNIGENT_OIDC_TOKEN_ENDPOINT_AUTH_METHOD=none` explicitly. #### Explicit endpoints (skip discovery) When discovery is unavailable, or advertises endpoints that don't suit your deployment, set **all three** overrides and the server skips the discovery fetch at startup: ``` OMNIGENT_OIDC_ISSUER=https://identity.example.com OMNIGENT_OIDC_CLIENT_ID=omnigent OMNIGENT_OIDC_TOKEN_ENDPOINT_AUTH_METHOD=none OMNIGENT_OIDC_AUTHORIZATION_ENDPOINT=https://login.example.com/authorize OMNIGENT_OIDC_TOKEN_ENDPOINT=https://tokens.example.com/token OMNIGENT_OIDC_JWKS_URI=https://keys.example.com/jwks OMNIGENT_OIDC_REDIRECT_URI=https://omnigent.example.com/auth/callback OMNIGENT_OIDC_COOKIE_SECRET=<64-hex-chars> ``` - Setting only one or two of the overrides fails at startup rather than mixing endpoint sources; leave all three unset to keep using discovery. - Overrides work with confidential clients too, and public-client mode works with discovery — the two options are independent. - Overrides are not supported for GitHub login, which keeps its fixed endpoints. - Endpoint hosts may differ from the issuer, but the ID token's `iss` must still match `OMNIGENT_OIDC_ISSUER` exactly and its `aud` must match the client ID. - Query parameters on the authorization endpoint (for example `?p=policy`) are preserved. Omnigent's own OAuth parameters win on name collisions. **Only trusted operators should set these addresses.** The token endpoint receives authorization codes and, for confidential clients, the client secret; the JWKS endpoint decides which signing keys are trusted. Each override must be an absolute HTTPS URL with no embedded credentials or fragment. Plain HTTP is accepted only for `localhost`, `127.0.0.1`, or `[::1]` for local testing. #### Signing algorithms ID tokens signed with **PS256** are verified alongside the existing RSA (`RS256`/`RS384`/`RS512`) and EC (`ES256`/`ES384`/`ES512`) algorithms. #### Verifying the setup Restart the server with the new settings, sign in from a private browser window, then run `omni login ` and complete the browser flow. Both should identify the same expected account. If your enterprise directory omits `email_verified`, the separate `OMNIGENT_OIDC_SKIP_EMAIL_VERIFICATION=1` setting (see [Custom email claim](#custom-email-claim)) remains an explicit operator choice — the options above never enable it for you. ## Header-based auth If your server sits behind an existing SSO proxy (e.g. OAuth2 Proxy, Cloudflare Access) that injects a trusted header, Omnigent can read the user identity directly from `X-Forwarded-Email`. No additional auth configuration is needed on the Omnigent side. ## Access control Once auth is enabled, control who can sign in and what they can access. Scope depends on the auth mode. `allowed_domains` applies to OIDC sign-ins only; leaving it empty means no domain restriction. Built-in accounts are invite-only, so domain allowlists do not apply there, and header-based auth delegates identity entirely to your proxy. The `admins` list works in both built-in accounts and OIDC modes. ### Admins and the management surface Admins are the identities in the `admins` list (above), plus anyone you add to the runtime-editable `/admins` file (reloaded without a restart). Listed identities are promoted to admin on their next sign-in. Admins get three management sections in the web UI under **Settings**, in both built-in accounts and OIDC modes: - **Settings > Members** — the user roster (username, role, last login). - **Settings > Policies** — server-wide [contextual policies](https://omnigent.ai/docs/policies/overview). - **Settings > Sharing** — the server-wide session-sharing policy (see [Session sharing](#session-sharing) below). In built-in accounts mode, **Members** also carries the management actions (`Invite member`, per-user `Reset` password, and `Remove`); under OIDC it is a read-only roster, since identities are owned by your IdP and provisioned on first sign-in. Separately, every signed-in account (built-in or OIDC) gets a **Settings > Account** section showing who they're signed in as, with a sign-out control and, in built-in accounts mode only, `Change password`. Both `Reset` and `Change password` sign the account out everywhere else — see [Changing or resetting a password](#changing-or-resetting-a-password). ### Removing a member In built-in accounts mode, `Remove` (`DELETE /auth/users/{id}`) revokes everything the account could still use to act, in one database transaction. Every authenticated request re-checks the stored account state — including on other server replicas — so there is no cache to wait out. After removal: - **Sign-in stops immediately.** Existing browser cookies and JWTs are rejected, and the account's device/refresh grants (`omni login`, mobile and desktop sign-ins) are revoked, so token refresh fails with `invalid_grant`. Outstanding invite and magic-login links created for or by the user are deleted. - **Saved authority is removed.** Session-sharing grants, saved provider connections ([GitHub](https://omnigent.ai/docs/deploy/github-connect), [Databricks](https://omnigent.ai/docs/deploy/databricks-connect)), projects, and daily spending/budget checkpoints are deleted. Sessions that lived in the removed user's projects are detached from the project, not deleted. - **Scheduled tasks are disabled.** The user's [scheduled tasks](https://omnigent.ai/docs/build/scheduled-tasks) are marked deleted and never fire again. - **Hosts are removed.** Hosts the user registered are torn down as if deleted from the host list: bound sessions are detached and launch credentials cleared. A managed cloud-sandbox host is kept as a tombstone until its sandbox is confirmed terminated. - **Session history is retained.** Conversations stay in the database, but no new runner can be launched for them on the removed owner's behalf. Removal is a database commit boundary, not a kill switch: work that was already admitted before the removal committed may finish, but nothing new can be granted to that identity afterward. You cannot remove your own account, and the last remaining admin cannot be removed. Usernames are reusable. If you later invite the same username again, it becomes a brand-new account: it does not inherit the old account's shared sessions, connections, projects, or session ownership, and the old cookies still fail. #### Provisioning a password for a shared-with user Sharing a session with a username that has never registered creates a passwordless entry for that person. To let them sign in, use `Reset` on that row in **Settings > Members**: the first reset provisions their credentials while preserving the sessions already shared with them, and they can log in with the returned password. A later reset rotates the password and [signs them out everywhere](#changing-or-resetting-a-password). #### Upgrading an existing accounts deployment The release that introduces this revocation model adds a schema migration that stamps existing accounts and their saved credentials. Accounts-mode deployments must upgrade every server and scheduler that shares the database together — old code does not understand the new columns, so mixed versions are unsafe. 1. Back up the database. 2. Stop all writers: every server and scheduler sharing the database. 3. Upgrade the application and run the migration. 4. Restart only the new version. 5. Sign in again in ordinary browser sessions, and restart any provider OAuth connection handshake that began before the upgrade. Existing refresh grants (CLI, mobile, desktop sign-ins) keep working and renew into upgraded tokens; ordinary cookies and JWTs require a fresh login. Do not roll back to the old version against the upgraded schema. ### Changing or resetting a password In built-in accounts mode, a signed-in session lasts `OMNIGENT_ACCOUNTS_SESSION_TTL_HOURS` (default 8). To end one user's sessions sooner, change the password (**Settings > Account > Change password**, `POST /auth/users/me/password`) or have an admin reset it (**Settings > Members > Reset**, `POST /auth/users/{id}/reset`). Either one invalidates everything issued to that user so far: browser sessions, CLI logins (`omni login`), refresh tokens, and unused magic sign-in links. A device sign-in approved before the change (a mobile, desktop, or Slack connection, for example) can no longer be completed afterwards. As with [removal](#removing-a-member), every authenticated request re-checks the stored account state on every server replica, so there is no cache to wait out. Two exceptions keep the caller from locking themselves out: - A self-serve password change keeps the browser that made it signed in. A caller that changed the password with a bearer token must log in again. - An admin resetting their _own_ password keeps the session that performed the reset, so they can save the returned password before signing in again. Unlike removal, the user's [scheduled tasks](https://omnigent.ai/docs/build/scheduled-tasks) and host registrations are kept — but that does not mean they keep running. A host that is already connected keeps its connection and still looks online, yet launches on it fail, including scheduled runs, until it reconnects: - A host signed in with the user's credentials (`omni host` after `omni login`) cannot reconnect with them. Run `omni login` again, then restart the host. - A managed [cloud sandbox host](https://omnigent.ai/docs/deploy/cloud-sandbox-host), which the server starts in a sandbox, connects with its own launch token rather than the user's credentials. It needs no login: once it reconnects with that token, before the token expires, launches work again. CLIs need `omni login` again too. Servers running a release without this behaviour keep the user's sessions when they change or reset a password. During a rolling upgrade, repeat a change made while such instances were still serving. #### Signing everyone out Rotating `OMNIGENT_ACCOUNTS_COOKIE_SECRET` signs out every user at once: it invalidates all session tokens, CLI logins, and refresh tokens. Unused sign-in and invite links are looked up by value, not signed with the secret, so they keep working until they expire — sign-in links after `OMNIGENT_ACCOUNTS_MAGIC_TTL_MINUTES` (default 10), invites after `OMNIGENT_ACCOUNTS_INVITE_TTL_HOURS` (default 72). ### Session sharing Admins set a **server-wide sharing policy** that governs how users can share sessions with each other. It applies to every session on the server and changes only _new_ grants — revoke, listing existing grants, session ownership, and grants already in place keep working in every mode. Configure it two ways: - **At boot**, with the `OMNIGENT_SHARING_MODE` environment variable. - **At runtime**, from **Settings > Sharing** in the web UI. This persists an override that takes effect immediately, without a restart. The **sharing mode** has four tiers: | Mode | Behavior | | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | `on` | Grants at any level (read / edit / manage) plus public and workspace read. This is the default. | | `read_only` | New grants are capped at read (view); edit and manage grants are rejected. | | `restricted_read_only` | Read-only, and sessions whose working directory is a user home directory or the filesystem root cannot be shared at all — not even read. | | `off` | Sharing is disabled. No new grants can be created and the **Share** control is hidden. | ``` OMNIGENT_SHARING_MODE=read_only omni server start ``` An unset or unrecognized value fails open to `on`. Under `restricted_read_only`, a workspace is considered too broad to share when its path is the filesystem root (`/`), root's home (`/root`), or a direct child of a home parent such as `/home/alice`, `/Users/bob`, or `/var/home/carol`; a subdirectory of a home (e.g. `/home/alice/proj`) stays shareable. #### Public access Separately from the tiers above, a **public-access** switch controls whether a session can be shared with _anyone who has the link_ (public read access). It is independent of the sharing mode — you can keep normal user-to-user sharing on while disabling public links. ``` OMNIGENT_PUBLIC_SHARING=off omni server start ``` Public access is enabled unless the value is explicitly falsy (`0` / `false` / `no` / `off`); unset or unrecognized fails open to enabled. It is also editable at runtime from **Settings > Sharing**. When disabled, the Share dialog hides its **Public access** toggle and the server rejects new public grants; sessions already shared publicly stay public until revoked. #### Default visibility for new sessions By default every session starts private and its owner shares it explicitly. A third setting, **Default visibility for new sessions**, lets an admin make new sessions start with public read access instead. It has three options: | Value | Settings > Sharing label | Behavior | | --------- | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | | `off` | Private | New sessions start private. Owners share them explicitly. This is the default. | | `sandbox` | Cloud sandbox sessions public | Sessions running in a server-managed [cloud sandbox](https://omnigent.ai/docs/deploy/cloud-sandbox-host) start public; sessions on a user's own machine stay private. | | `all` | All sessions public | Every new session starts with public read access. | ``` OMNIGENT_DEFAULT_PUBLIC_SESSIONS=sandbox omni server start ``` Unlike the two settings above, this one fails **closed**: an unset or unrecognized value resolves to `off`, so a typo never exposes sessions. It is also editable at runtime from **Settings > Sharing**, or via `default_public_sessions` on `PUT /v1/sharing`. The default is applied when a session is created, forked, or fired by a [scheduled task](https://omnigent.ai/docs/build/scheduled-tasks) (a task whose execution target is `managed_sandbox` counts as a sandbox session). It is only the creation default: owners can revoke the public grant from the Share dialog at any time, and existing sessions are never changed. A few sessions are always left private: - Sub-agent sessions, which follow their parent's grants instead. - Side chats, which are private scratch forks of the caller's own view. - Single-user (no-auth) servers, where there is no sharing at all. The default never grants more than an owner could grant by hand. It is inactive while the sharing mode is `off` or public access is disabled, and under `restricted_read_only` it skips sessions whose working directory is too broad to share (see above). **Settings > Sharing** greys out the control and shows the effective value (`Private`) while a higher-level setting overrides it; the saved choice returns once the blocker is lifted. When a deployment manages the sharing mode, public access, or default visibility itself (for example a managed service that injects its own policy), **Settings > Sharing** shows the current value as read-only and cannot change it here. ### Domain allowlist Restrict sign-ups to specific email domains. In your server config (`/data/config.yaml`): ``` allowed_domains: [yourcompany.com] admins: [you@yourcompany.com] ``` ### Invite outsiders Need to let in someone outside your domain, like a contractor? Set `OMNIGENT_OIDC_ALLOW_INVITES=1` and send them a one-time invite link. ## Migration Already using one auth mode and want to switch? These commands move your existing users across without losing sessions or admin rights. ### From built-in accounts to SSO If you started with built-in accounts and want to switch to OIDC, one command brings everyone across so they keep their sessions and admin rights: ``` omni debug migrate-accounts-to-oidc --domain yourcompany.com --commit ``` Without `--commit` the command is a dry run that reports what would change without modifying anything. --- ## https://omnigent.ai/docs/deploy/cloud-sandbox-host # Cloud Sandbox Host A cloud sandbox host moves the Omnigent runner from your laptop to a remote container. Your agent keeps working after you close your laptop, in an isolated environment with cloud compute. Looking to restrict what your agent can access on the filesystem and network instead? That's [Omnibox](https://omnigent.ai/docs/omnibox). | | Local runner | Cloud sandbox host | | ---------------- | ------------------------ | ---------------------- | | **Availability** | Stops when laptop closes | Runs independently | | **Compute** | Your CPU and memory | Cloud compute | | **Environment** | Local files | Isolated, reproducible | | **Best for** | Interactive work | Long-running tasks | Omnigent currently supports [Databricks Sandbox](https://docs.databricks.com/aws/en/compute/serverless/sandbox), [Modal](https://modal.com), [Daytona](https://www.daytona.io), [Blaxel](https://blaxel.ai), [Islo](https://islo.dev), [Gensee](https://gensee.ai), [NVIDIA OpenShell](https://github.com/NVIDIA/openshell), and [Boxlite](https://github.com/boxlite-ai/boxlite) for cloud sandbox hosts. The hosted-platform comparison below covers Databricks, Modal, Daytona, Blaxel, and Islo. OpenShell connects to your selected gateway; Boxlite can use a local micro-VM runtime or a remote `boxlite serve` pool. Gensee and Databricks are managed-host providers only: their control planes start the host over HTTPS, so they support the server-managed flow but not the CLI bootstrap commands. Databricks sandboxes are driven through the [Databricks Sandbox SDK](https://databricks-sdk-py.readthedocs.io/en/latest/workspace/sandbox/sandbox.html). For local microVMs, use [Microsandbox](https://omnigent.ai/docs/reference/configuration/cloud-sandbox#microsandbox). Its VMs run on the machine running the server or CLI, so they depend on that machine staying awake. | | [Databricks Sandbox](https://docs.databricks.com/aws/en/compute/serverless/sandbox) | [Modal](https://modal.com) | [Daytona](https://www.daytona.io) | [Blaxel](https://blaxel.ai) | [Islo](https://islo.dev) | | ------------------------ | ----------------------------------------------------------------------------------- | ---------------------------------- | --------------------------------- | ---------------------------------------- | ---------------------------------------------- | | **Runner lifetime** | Idle-stops after configurable `inactivity_timeout_s`, resumed on next turn | 24 hours (platform cap) | No cap (runs until deleted) | 24h max age by default (raise via `ttl`) | No cap (idle-paused after 15 min when managed) | | **Credential injection** | Owner's own Databricks OAuth token | Named secrets (Modal secret store) | Env vars copied from server | Env vars copied from server | Env vars copied from server / gateway profile | | **Free-tier egress** | Full egress (not configurable) | Full egress | Allowlisted domains only | Full egress | Gateway profile controls egress | ## Server-managed (recommended) Configure your server once and anyone on the team can launch a cloud sandbox host from the web UI. ### 1. Install ``` pip install 'omnigent[databricks]' # for Databricks Sandbox pip install 'omnigent[modal]' # for Modal pip install 'omnigent[daytona]' # for Daytona pip install 'omnigent[blaxel]' # for Blaxel pip install 'omnigent[islo]' # for Islo pip install 'omnigent[openshell]' # for OpenShell pip install 'omnigent[boxlite]' # for Boxlite pip install 'omnigent[microsandbox]' # for local microVMs ``` Gensee needs no extra: it talks to the Gensee control plane over HTTPS and installs no provider SDK in Omnigent. The Islo launcher uses the Islo Python SDK, which ships in the `omnigent[islo]` extra and is imported lazily, so only users of the Islo provider need to install it. The `omnigent[blaxel]` extra ships the Blaxel Python SDK with the same lazy-import posture. To launch Blaxel sandboxes from the CLI you also need the [Blaxel CLI](https://docs.blaxel.ai/cli-reference/introduction) (`brew tap blaxel-ai/blaxel && brew install blaxel`). ### 2. Set provider credentials **Databricks:** Sandboxes run as each user's own Databricks account, so there is no shared provider key on the server. Each user connects under **Settings → Connections → Databricks**, which needs an account-level custom OAuth app integration configured on the server; see [Databricks settings](https://omnigent.ai/docs/reference/configuration/cloud-sandbox#databricks). **Modal:** run `modal setup` to authenticate, or set `MODAL_TOKEN_ID` and `MODAL_TOKEN_SECRET` on the server. **Daytona:** create an API key in the [Daytona dashboard](https://app.daytona.io) (Dashboard > Keys) and set it on the server: ``` export DAYTONA_API_KEY=dtn_… ``` **Islo:** create an API key with the [Islo CLI](https://docs.islo.dev) (`islo login`, then `islo api-key create omnigent --show`) and set it on the server. The SDK exchanges the key for short-lived session tokens and refreshes them automatically, so the API key is the only required runtime credential: ``` export ISLO_API_KEY=islo_key_… ``` **Blaxel:** log in to your workspace with the Blaxel CLI (`bl login your-workspace`), or set the control credentials on the server for a non-interactive process: ``` export BL_WORKSPACE=your-workspace export BL_API_KEY=your-api-key ``` **OpenShell:** select a gateway with `openshell gateway select `, or set `OPENSHELL_GATEWAY` on the server. The launcher uses that gateway connection and does not require a separate API key. **Gensee:** set the API token variable named by `sandbox.gensee.api_token_env` (`GENSEE_CONTROLLER_API_TOKEN` by default) on the server. Gensee currently provisions endpoints and tokens for contracted organizations; keep the token in the server process environment, not in `config.yaml`. ``` export GENSEE_CONTROLLER_API_TOKEN=… ``` **Boxlite:** local mode requires KVM on Linux and no provider credential. For a remote `boxlite serve` pool, set `BOXLITE_API_KEY` on the server and configure the pool endpoint in the [Boxlite settings](https://omnigent.ai/docs/reference/configuration/cloud-sandbox#boxlite). **Microsandbox:** no provider credentials or daemon are needed; the install extra includes the runtime. Use Apple Silicon macOS or glibc Linux with access to `/dev/kvm` (typically through the `kvm` group). Intel Macs are not supported. Provider control credentials stay on the launching process and never enter the sandbox. Web users log in to Omnigent, not the sandbox provider. ### 3. Configure a provider Add a `sandbox` section to your server config YAML (`~/.omnigent/config.yaml` on a laptop, `/data/config.yaml` in Docker). A typical Modal configuration looks like this: ```yaml sandbox: provider: modal server_url: https://your-server.example.com modal: image: ghcr.io/omnigent-ai/omnigent-host:latest # optional, official image by default secrets: [omnigent-llm] # Modal secrets with LLM API keys ``` See [common settings](https://omnigent.ai/docs/reference/configuration/cloud-sandbox#common-settings) for the top-level fields, [Modal settings](https://omnigent.ai/docs/reference/configuration/cloud-sandbox#modal) for the provider block, and [host configuration](https://omnigent.ai/docs/reference/configuration/cloud-sandbox#host-configuration) for `host_config`. The full [cloud sandbox configuration reference](https://omnigent.ai/docs/reference/configuration/cloud-sandbox) also covers the other providers and lifecycle settings. ### 4. Launch from the web UI Start a new session and select **New Sandbox** in the host picker. The server provisions the cloud host, starts the runner, and connects it back automatically. Optionally point the sandbox at a git repository to clone. Use the composer's **Repository** control to pick a repo from your [connected GitHub account](https://omnigent.ai/docs/deploy/github-connect) or paste a clone URL, and pin a branch; leave it empty for a bare sandbox with a server-created workspace. The repo is cloned inside the sandbox and becomes the session's working directory. Admins can make these clones faster with the optional [`sandbox.git_clone`](https://omnigent.ai/docs/reference/configuration/cloud-sandbox#git-clone-options) block, which limits history depth, branch selection, and blob fetching for fresh checkouts on providers that support it. Some providers can clone **several repositories** into one sandbox. When the selected provider supports it, keep adding entries in the **Repository** control (up to 10 repos). Omnigent clones them in parallel as siblings under the sandbox workspace: with a single repo the agent starts inside it, and with several the agent starts in the parent directory that holds them all. Two repos that would land in the same directory name (for example `org-a/api` and `org-b/api`) are disambiguated by owner. Multi-repo is a per-provider capability — the [Kubernetes managed sandbox](https://omnigent.ai/docs/reference/configuration/kubernetes) supports it, while single-repo providers accept only one repo and reject a request for several. The picker adapts to the selected provider, so it offers the multi-repo list only when that provider supports it. Calling the [REST API](https://omnigent.ai/reference) directly, pass a single repository in `workspace` or several in `workspaces` (a list of clone URLs, each optionally `#`) on `POST /v1/sessions`. The two are mutually exclusive, `workspaces` requires a managed session (`host_type: "managed"`), and it takes at most 10 repositories. ### 5. Run automations in sandboxes (optional) [Scheduled tasks](https://omnigent.ai/docs/build/scheduled-tasks#where-a-task-runs) can use the same launch path: create a task with `execution_target: managed_sandbox` (or pick **New Sandbox** in the automation dialog's host picker) and each firing provisions a fresh sandbox with a clean workspace. Existing managed sandbox hosts cannot be pinned as an automation's host, and the option is only offered when this server has a managed sandbox provider configured. An automation's sandbox follows the server's sandbox configuration exactly like one launched for a chat — the automation does not override timeouts or terminate the sandbox when a run finishes. Choose provider lifetime, idle, and cleanup settings (including the [reaper](#reap-stale-sandboxes-optional) below) that fit your automation frequency and resource budget; enabling managed sandboxes alone does not guarantee a short shutdown time. ## CLI Create and manage cloud sandbox hosts from the command line. The commands are the same across CLI-capable providers; switch the `--provider` flag: ``` omni sandbox create --provider modal # or --provider daytona / blaxel / islo / openshell omni sandbox connect --provider modal \ --sandbox-id --server ``` Databricks, Boxlite, and Gensee support the server-managed flow but not these CLI bootstrap commands. For Microsandbox, use `--provider microsandbox` and pass a `--server` URL reachable from the VM; see [Microsandbox settings](https://omnigent.ai/docs/reference/configuration/cloud-sandbox#microsandbox). For a Blaxel CLI launch, list any credentials the host or agent needs in `OMNIGENT_BLAXEL_SANDBOX_ENV` before `create`. Never include `BL_API_KEY` or `BL_CLIENT_CREDENTIALS`. Blaxel does not expose local callback port forwarding, so the in-sandbox browser login is skipped automatically. Stopping `connect` leaves the sandbox running. Delete it with the provider's CLI or console when you no longer need it. ### Reap stale sandboxes (optional) By default a managed sandbox lives until its session is deleted (or the provider's own lifetime cap reaps it). To automatically terminate sandboxes whose hosts have gone offline and stayed offline, add an optional `reaper` block to `sandbox:`. It is **deployment-wide** — configure it next to `provider` (or `providers:`), never inside an individual provider entry: ``` sandbox: provider: modal server_url: https://your-server.example.com reaper: # optional; disabled by default enabled: true # default: false terminate_after_offline_days: 30 # default: 30 days sweep_interval_s: 86400 # default: 1 day ``` - `enabled` — whether the server starts the reaper loop. Defaults to `false` because terminating user compute is destructive; opt in explicitly. - `terminate_after_offline_days` — the minimum number of days since a managed host's last heartbeat before its current sandbox generation may be terminated. Defaults to `30`. - `sweep_interval_s` — seconds between complete sweeps. Defaults to `86400` (one day). Both `terminate_after_offline_days` and `sweep_interval_s` must be positive integers. Setting `reaper` inside a `providers:` list entry is rejected at startup — one loop covers the whole deployment and dispatches each termination through the provider recorded on the managed host it is reaping. Reaping detaches only the stale sandbox generation: the session transcript and the durable host binding stay, so the next message to that session can launch a fresh sandbox under the same host identity. A provider termination that fails stays pending and is retried on a later sweep. The reaper requires a configured host store. If `reaper.enabled` is `true` but no host store is configured, the server logs a warning and the reaper does not run. ## Community sandbox providers The providers above ship with Omnigent. Beyond them, the server discovers additional sandbox providers at startup through a plugin registry, so a new hosting platform can be added as a **separate package** without changing the core `omnigent` package. An installed provider's name is accepted in `sandbox.provider`, and its provider-specific block is validated at server startup when it declares a config model. Configured community providers are offered as managed-launch options in the web UI, so they must implement that lifecycle. Install the plugin in the server's Python environment and restart to discover it. A missing or rejected plugin makes its provider name invalid in server configuration. ### Configure a contributed provider Set `sandbox.provider` to the contributed name and, if the provider declares a config model, add a matching `sandbox.` block: ``` sandbox: provider: acme server_url: https://your-server.example.com acme: namespace: sandboxes ``` The block is validated against the provider's declared config model when the server starts. Whether unknown keys are rejected depends on that model. A provider that declares no config model receives no provider-specific config arguments. ### Build a provider plugin Follow [Community Sandbox Providers](https://omnigent.ai/docs/deploy/community-sandbox-providers) for the implementation guide: choose an exec-model or provider-controlled launcher, define validated configuration, register the package entry point, and test managed startup and cleanup. The guide also covers optional CLI bootstrap, resume, and multi-repository support. --- ## https://omnigent.ai/docs/deploy/community-sandbox-providers # Community Sandbox Providers Add a new place to run Omnigent hosts without changing the core package. A third-party Python package registers a provider through the `omnigent.sandbox_providers` entry-point group. Once installed and configured on the server, it can serve managed sessions from the web UI or API. A sandbox provider controls **where the runner executes**. It is separate from [Omnibox](https://omnigent.ai/docs/omnibox), which restricts what agent commands can access inside that environment. To use an existing provider rather than build one, start with [Cloud Sandbox Host](https://omnigent.ai/docs/deploy/cloud-sandbox-host). ## Choose a launcher model Every registered launcher must subclass `SandboxHostLauncher`, directly or through `ExecModelHostLauncher`. Both live in `omnigent.onboarding.sandboxes.base`. | Base class | Use when | What you implement | | ----------------------- | ------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------- | | `ExecModelHostLauncher` | Your platform creates a sandbox and exposes shell execution inside it. | `prepare()`, `provision()`, and `run()`, plus cleanup with `terminate()`. Core supplies `start_host()`. | | `SandboxHostLauncher` | Your container starts the host as its entrypoint, or your control plane starts it for you. | `prepare()`, `provision()`, `start_host()`, and cleanup with `terminate()`. No exec transport is required. | Start with **managed sessions only**. CLI bootstrap is a separate, optional feature that needs file transfer, foreground execution, and additional lifecycle methods. The example below deliberately leaves it disabled. ## 1. Create the package Use a unique provider id, such as `acme`. Put implementation modules under `omnigent.community.sandbox.`: ```text omnigent-acme/ ├── pyproject.toml └── src/ └── omnigent/ └── community/ └── sandbox/ └── acme/ ├── __init__.py ├── config.py ├── launcher.py └── plugin.py ``` Keep `acme/__init__.py` empty. Do not ship replacements for core's `omnigent/__init__.py`, `omnigent/community/__init__.py`, or `omnigent/community/sandbox/__init__.py`. Core provides the shared namespace; your distribution supplies only its own subtree. Here is a setuptools-based package definition: ```toml # pyproject.toml [build-system] requires = ["setuptools>=68"] build-backend = "setuptools.build_meta" [project] name = "omnigent-acme" version = "0.1.0" requires-python = ">=3.12" dependencies = ["omnigent", "pydantic>=2", "click>=8"] [project.entry-points."omnigent.sandbox_providers"] acme = "omnigent.community.sandbox.acme.plugin:get_contribution" [tool.setuptools.packages.find] where = ["src"] include = ["omnigent.community.sandbox.acme*"] namespaces = true ``` Add your provider SDK to the dependencies, and constrain the Omnigent dependency to the versions you test before publishing. These examples follow the current [source contract](https://github.com/omnigent-ai/omnigent/blob/main/omnigent/onboarding/sandboxes/base.py); check that your target release exposes the same API. ## 2. Define provider configuration A `config_model` validates the server's `sandbox.acme` block and is passed to the launcher as `config=`. Use a Pydantic model in your provider namespace: ```python # src/omnigent/community/sandbox/acme/config.py from pydantic import BaseModel, ConfigDict, Field class AcmeConfig(BaseModel): model_config = ConfigDict(extra="forbid") namespace: str = Field(default="sandboxes", min_length=1) ``` `extra="forbid"` rejects misspelled keys rather than silently ignoring them. Validation runs when the server parses its configuration and again when the registry constructs the launcher. If the block is absent, the model receives no arguments, so provide defaults for optional settings. The model is optional. Without one, the registry calls the launcher with **no arguments** and does not pass through the provider-specific block. Do not expect arbitrary YAML keys to become constructor arguments. Keep provider control credentials in the launching process's environment or credential store, not in this example's YAML. Resolve them in `prepare()` and never copy the server's entire environment into a sandbox. ## 3. Implement the launcher This is an **interface skeleton**, not a working Acme integration. Replace the four `NotImplementedError` bodies with calls to your platform's SDK or API before attempting a launch. ```python # src/omnigent/community/sandbox/acme/launcher.py from omnigent.onboarding.sandboxes.base import ( ExecModelHostLauncher, RemoteCommandResult, ) from omnigent.onboarding.sandboxes.types import SandboxCapabilities from .config import AcmeConfig class AcmeSandboxLauncher(ExecModelHostLauncher): provider = "acme" def __init__(self, *, config: AcmeConfig) -> None: self.config = config @property def capabilities(self) -> SandboxCapabilities: return SandboxCapabilities( managed_launch=True, programmatic_terminate=True, ) def prepare(self) -> None: """Load the SDK and verify provider credentials; safe to repeat.""" raise NotImplementedError("Implement provider preflight") def provision(self, name: str) -> str: """Create a sandbox in self.config.namespace and return its id.""" raise NotImplementedError("Implement sandbox creation") def run( self, sandbox_id: str, command: str, *, check: bool = True ) -> RemoteCommandResult: """Run a shell command; raise on nonzero exit only when check=True.""" raise NotImplementedError("Implement shell execution") def terminate(self, sandbox_id: str) -> None: """Delete the sandbox; an already-absent sandbox is success.""" raise NotImplementedError("Implement idempotent deletion") ``` Keep constructors free of network calls and resource creation: the server also constructs launchers to read their capabilities for `/v1/info`. Keep `plugin.py` and `config.py` imports light; load optional SDKs in `prepare()` or the methods that use them. ### Method contracts - **`prepare()`** validates tooling and credentials without provisioning compute. It must be safe to repeat. Surface expected operational failures as `click.ClickException` with the provider name and a useful, secret-free message. - **`provision(name) -> str`** returns an opaque sandbox id, not a `SandboxInfo` or SDK object. Clean up partial allocations if creation fails before you can return an id; the caller cannot clean up an id it never received. - **`run(..., check=True) -> RemoteCommandResult`** returns `returncode`, `stdout`, and `stderr`. With `check=True`, raise `click.ClickException` on a nonzero exit. With `check=False`, return the failure result. If the SDK takes an argv list, invoke a shell with `["sh", "-c", command]` rather than splitting the command on spaces. Do not log raw command strings: host-start commands carry credentials. - **`terminate(sandbox_id)`** releases compute and other provider resources. Treat “already deleted” as success, but do not hide authorization errors or transient failures. Cleanup can retry, and it may use a fresh launcher that did not provision the sandbox; resolve resources from the id and configured credentials, not only an in-memory handle. Do not require an earlier `prepare()` call on the teardown instance. ### What the exec-model base supplies Inherited `start_host()` uses your `run()` to: 1. Resolve `$HOME` and create `$HOME/workspace`. 2. Clone requested repositories through `materialize_workspace()`. 3. Apply deployment-supplied `host_config` before starting the host. 4. Launch `omnigent host --server ...` with the supplied host identity and launch token in its environment. 5. Return the absolute workspace path: the clone directory for one repository, otherwise the parent workspace. Provision an image with Omnigent already installed, the harnesses you support, and the shell tools needed by this flow. The official `ghcr.io/omnigent-ai/omnigent-host:latest` image is a starting point; pin a published image tag for reproducible deployments. Managed startup does not build or install Omnigent wheels for you. The default `run_background()` uses `setsid`, `nohup`, and a shell supervisor, writing host output to `/tmp/omnigent-host.log`. If your platform kills child processes when an exec request ends, override `run_background()` with a provider-supported long-lived process mechanism. Do not report success for a host that immediately dies when the exec session closes. Override `materialize_workspace()` if you need a different way to obtain a checkout; you need not replace all of `start_host()` for that. ### Providers without remote exec Subclass `SandboxHostLauncher` directly and implement this keyword contract: ```python from collections.abc import Callable, Sequence from omnigent.onboarding.sandboxes.types import RepoWorkspace def start_host( self, sandbox_id: str, *, token: str, host_id: str, host_name: str, server_url: str, repos: Sequence[RepoWorkspace] = (), host_config: dict[str, object] | None = None, on_stage: Callable[[str], None] | None = None, ) -> str: # Launch through your container entrypoint or control-plane API. raise NotImplementedError("Return the absolute in-sandbox workspace path") ``` Your implementation must arrange for `omnigent host --server ` to run with `OMNIGENT_HOST_TOKEN`, `OMNIGENT_HOST_ID`, and `OMNIGENT_HOST_NAME` set to the supplied values. Install `host_config` before the host starts; the shared `render_host_config_write_command()` helper implements core's injection and replacement semantics when you can run an initialization command. `RepoWorkspace` supplies `url`, `branch`, and `repo_name`. Materialize requested repositories and return the same workspace-path convention as the exec model. When supplied, call `on_stage("cloning")` before cloning and `on_stage("starting")` before host startup. For platforms that boot the host immediately when compute is created, `provision()` can reserve an id and defer actual creation to `start_host()`. This lets Omnigent register the launch token **before** the new host connects. Do not block `start_host()` for the host's entire lifetime; after it returns, the server waits for online registration. See the built-in [Kubernetes launcher](https://github.com/omnigent-ai/omnigent/blob/main/omnigent/onboarding/sandboxes/kubernetes.py) and [Gensee launcher](https://github.com/omnigent-ai/omnigent/blob/main/omnigent/onboarding/sandboxes/gensee.py) for the entrypoint and control-plane approaches, respectively. ## 4. Register the contribution ```python # src/omnigent/community/sandbox/acme/plugin.py from omnigent.onboarding.sandboxes.registry import ( SandboxProviderContribution, SandboxProviderMetadata, ) from .config import AcmeConfig def get_contribution() -> SandboxProviderContribution: return SandboxProviderContribution( name="omnigent-acme", providers={ "acme": SandboxProviderMetadata( name="acme", launcher_class=( "omnigent.community.sandbox.acme.launcher:AcmeSandboxLauncher" ), config_model=AcmeConfig, ), }, ) ``` The contribution name identifies the package in diagnostics. The provider key, metadata `name`, and launcher's `provider` must agree. One contribution can register several providers, but it cannot override an already registered name. The registry requires the launcher import path and the config model's module to be under `omnigent.community.sandbox.*`. The launcher is referenced by import string so it need not load during entry-point discovery. `SandboxProviderMetadata` also accepts **`managed_token_ttl_s`**, the managed host launch-token lifetime in seconds. If omitted, it currently defaults to 25 hours. Choose a positive value appropriate for your sandbox lifetime: this is an authentication lifetime, not a provider-side timeout or an instruction to delete compute. ## 5. Install and configure Install the plugin into the **same Python environment as the Omnigent server**. From your plugin checkout: ```bash uv pip install -e . ``` For a published plugin, install its distribution there instead. If Omnigent is installed as a uv tool, add the plugin to that tool's environment with `uv tool inject omnigent omnigent-acme`. Restart the server after installing or upgrading: registry discovery is cached per process. Configure the server: ```yaml sandbox: provider: acme server_url: https://your-server.example.com acme: namespace: sandboxes ``` `sandbox.server_url` must be reachable from the sandbox and support the host's connection back to Omnigent. The provider SDK authenticates on the server; the host receives a separate server-minted launch token. Keep provider control credentials out of the guest, and use a secure environment or secret mechanism for the host token rather than logging it or putting it in process arguments. Once the launcher is implemented, select **New Sandbox** in the web UI. The diagram shows the normal managed lifecycle alongside failure cleanup, idle resume, deletion, and stale-sandbox reaping. _Diagram: Managed sandbox provider lifecycle, from provisioning through startup, resume, and termination_ ```mermaid sequenceDiagram actor User participant Core as Omnigent server participant Provider as Provider launcher participant Sandbox as Remote sandbox User->>Core: Request managed session Core->>Provider: prepare() Provider-->>Core: Preflight ready Core->>Provider: provision(name) Provider-->>Core: sandbox_id Note over Provider,Sandbox: If creation fails, the provider cleans any partial allocation Core->>Core: Register host identity and launch token Core->>Provider: start_host(sandbox_id, token, ...) Provider->>Sandbox: Start omnigent host alt Host connects before timeout Sandbox->>Core: Register with host identity and token Core-->>User: Host online — session runs alt Provider idle-stops the sandbox Provider->>Sandbox: Stop compute and retain workspace User->>Core: Send the next message Core->>Provider: resume(sandbox_id) Provider->>Sandbox: Restore the same sandbox and workspace Core->>Core: Re-arm launch token Core->>Provider: start_host(..., repos=()) Provider->>Sandbox: Restart omnigent host Sandbox->>Core: Reconnect Note over Core,Sandbox: A failed wake preserves the sandbox and workspace for another retry else Session deleted or stale reaper runs Core->>Core: Revoke token and delete or detach generation Core->>Provider: terminate(sandbox_id) Provider->>Sandbox: Release compute end else Startup fails or online wait times out Core->>Core: Revoke token and delete failed generation Core->>Provider: terminate(sandbox_id) Provider->>Sandbox: Release compute end Note over Core,Provider: terminate() is idempotent — transient failures remain pending and retry later ``` Omnigent handles host registration, token issuance, and the session binding. Your provider handles compute, workspace setup, process startup, and teardown. A failed wake preserves the stopped sandbox and its workspace for another attempt. Deletion and reaping call the provider's idempotent `terminate()`; transient teardown failures remain pending for a later retry. See [host configuration](https://omnigent.ai/docs/reference/configuration/cloud-sandbox#host-configuration) for configuring the runner, and the [cloud-host guide](https://omnigent.ai/docs/deploy/cloud-sandbox-host#reap-stale-sandboxes-optional) for the optional stale-sandbox reaper. ## Optional capabilities Return an explicit `SandboxCapabilities` object. Its fields default to `False`; set a flag only when the corresponding behavior is implemented and tested. | Capability | Contract | | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | | `managed_launch` | Implements the server-managed `prepare` / `provision` / `start_host` flow. | | `programmatic_terminate` | Implements idempotent `terminate()`. Needed for reliable automatic cleanup. | | `cli_bootstrap` | Supports the separate `omnigent sandbox create` / `connect` flows described below. | | `file_copy` | Implements `put(sandbox_id, local_path, remote_path)`. | | `streaming_exec` | Implements `stream_exec(..., pty=False)` returning a `RemoteProcess`. | | `foreground_exec` | Implements `exec_foreground(sandbox_id, command) -> int`, including Ctrl-C cleanup. | | `local_port_forward` | Implements a `forward_local_port()` context manager for local-to-sandbox callback forwarding. | | `resume_stopped` | Implements `resume(sandbox_id)` to restore compute and persistent storage under the same id. Core restarts the host separately. | | `snapshot_restore` | Resume restores a suspend-time snapshot rather than a cold start; meaningful with `resume_stopped`. | | `multi_repo` | Handles multiple `RepoWorkspace` entries and returns their parent workspace. Explicit opt-in, including for exec-model subclasses. | | `classifies_runner_by_agent` | Accepts an additional `agent_name` keyword in `start_host()` and stamps the resolved built-in agent into provider metadata. | The server currently treats configured community providers as managed-launch options. `managed_launch=False` is **not** a way to hide a configured plugin from the picker; only configure providers that actually implement managed launch. If your platform needs expiry refreshes, implement a cheap, idempotent `keep_alive()`; the server calls it periodically for live managed runners. `is_running()` may return `True`, `False`, or `None` when status is unknown. The reaper can reuse one launcher per workspace and provider. Override `reaper_identity(workspace_id)` with a context manager if background termination requires workspace-scoped credentials. Termination must still succeed when the sandbox has already disappeared. ### Adding CLI bootstrap CLI support requires an `ExecModelHostLauncher` subclass with `cli_bootstrap=True`. Implement `attach()`, `keep_alive()`, `put()`, `wheel_install_command()`, and `exec_foreground()` in addition to the basic exec-model methods. Supporting the in-sandbox App OAuth flow also requires `stream_exec()` and `forward_local_port()`. `RemoteProcess.lines` must return the same iterator across accesses; its `close()` must be idempotent. The CLI constructs a community provider with the config model's defaults, not the server's `sandbox.acme` block. Supply usable defaults or resolve CLI-specific configuration in your launcher. `create` builds and ships wheels from an Omnigent source checkout; a plugin installation alone does not supply that checkout. After implementing CLI support, use: ```bash omnigent sandbox create --provider acme \ --server https://your-server.example.com \ --repo-root /path/to/omnigent omnigent sandbox connect --provider acme \ --sandbox-id YOUR_SANDBOX_ID \ --server https://your-server.example.com ``` Without local port forwarding, `create` skips the in-sandbox login flow; it does not bypass authentication required by the target server. Managed-only plugins, including the skeleton above, should use the web UI/API flow instead. ## Verify and troubleshoot First check discovery and config construction without allocating compute. Run this with the Python interpreter from the environment where you installed the plugin: ```python from omnigent.onboarding.sandboxes.registry import instantiate, plugin_state state = plugin_state() print("Registered providers:", state.names()) print("Plugin load errors:", state.load_errors) assert "acme" in state, state.load_errors launcher = instantiate("acme", config={"namespace": "test-sandboxes"}) assert launcher.provider == "acme" assert launcher.config.namespace == "test-sandboxes" assert launcher.capabilities.managed_launch assert not launcher.capabilities.cli_bootstrap ``` Then test your implementation with SDK fakes and an isolated provider account: - Reject unknown config keys and invalid values before allocating compute. - Verify `run()` captures output and obeys both values of `check`. - Start a managed session with an empty workspace, then one with a repository and branch. Verify the returned workspace and that the host comes online. - Verify the host survives the provisioning/exec connection closing. - Exercise failed startup and deletion; confirm resources are released. Call `terminate()` twice, including from a newly constructed launcher. - Test resume, multi-repo workspaces, and CLI bootstrap only if advertised. - Inspect logs and provider operation records for accidental token exposure. For registry and lifecycle test examples, see [`test_registry.py`](https://github.com/omnigent-ai/omnigent/blob/main/tests/onboarding/sandboxes/test_registry.py), [`test_base.py`](https://github.com/omnigent-ai/omnigent/blob/main/tests/onboarding/sandboxes/test_base.py), and [`test_managed_hosts.py`](https://github.com/omnigent-ai/omnigent/blob/main/tests/server/test_managed_hosts.py). | Symptom | Check | | --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Provider is absent from `state.names()` | Confirm the package is installed in the server's environment, its entry-point group is exactly `omnigent.sandbox_providers`, and discovery has restarted. Inspect `state.load_errors`. | | Namespace or duplicate-name error | Use your own `omnigent.community.sandbox.*` subtree for both launcher and config model; choose a unique provider id. | | `not a SandboxHostLauncher subclass` | Register a subclass of `SandboxHostLauncher` or `ExecModelHostLauncher`, not a transport-only class. | | Invalid `sandbox.acme` config | Match the model's fields and types. An absent block still must satisfy the model's required fields. | | Host never comes online | Check server reachability, the image's Omnigent installation, host identity/token delivery, and whether the host process survives exec completion. | A broken entry point is logged and recorded in `load_errors` without stopping registry discovery. However, **configuring a missing or rejected provider still fails server config validation** because its name is unknown. Launcher import and construction happen later, so a registered name alone does not prove that its SDK dependencies or runtime are usable. Plugins execute Python in the Omnigent server process. The namespace rule is a packaging constraint, not a security sandbox. Install only provider packages you trust. --- ## https://omnigent.ai/docs/deploy/credential-store # Credential Store The Omnigent server can persist per-user integration credentials encrypted at rest. Encryption is handled by a pluggable **cipher backend**, selected per server. Two backends ship: - **AWS KMS** — for deployments on AWS. - **HashiCorp Vault Transit** — the provider-agnostic alternative for deployments that don't run on AWS. Each secret is bound to the identity that owns it — `(workspace_id, user_id, provider, account_id)` — so a stored credential only decrypts under the same identity. AWS KMS enforces this with its encryption context; Vault Transit enforces it with a derived key. When no backend is configured, the credential store is disabled. ## Choosing a backend Set `OMNIGENT_CREDENTIAL_CIPHER` to pick a backend explicitly: | Value | Backend | Install extra | | ------- | ----------------------- | ----------------- | | `kms` | AWS KMS | `omnigent[kms]` | | `vault` | HashiCorp Vault Transit | `omnigent[vault]` | When the selector is set, the chosen backend's key environment variable is required — a mismatch raises rather than silently disabling the store. An unknown value also raises. Leave `OMNIGENT_CREDENTIAL_CIPHER` unset to auto-detect the single configured backend as a zero-config convenience. Configuring more than one backend without the selector is an error (there is no silent precedence); configuring none disables the credential store. Both SDKs — boto3 for KMS, hvac for Vault — are imported lazily, only when the matching backend is selected. The backend-agnostic base store needs neither dependency, and a deployment that uses one backend never installs the other. ## AWS KMS Install the extra and point the store at a KMS key: ``` pip install 'omnigent[kms]' ``` | Variable | Purpose | | -------------------------------- | ------------------------------------------------------------------------------------------------- | | `OMNIGENT_CREDENTIAL_KMS_KEY_ID` | KMS key id or alias used to encrypt credentials. | | `OMNIGENT_CREDENTIAL_CIPHER` | Set to `kms` to select this backend explicitly (optional when it is the only backend configured). | boto3 resolves AWS credentials and region from the standard AWS environment. No SDK import or AWS call happens until the first encrypt or decrypt. ## HashiCorp Vault Transit Install the extra and point the store at a Vault Transit key: ``` pip install 'omnigent[vault]' ``` | Variable | Purpose | | --------------------------------- | --------------------------------------------------------------------------------------------------- | | `OMNIGENT_CREDENTIAL_VAULT_KEY` | Name of the Transit key used to encrypt credentials. | | `OMNIGENT_CREDENTIAL_VAULT_MOUNT` | Transit mount path. Optional; defaults to `transit`. | | `OMNIGENT_CREDENTIAL_CIPHER` | Set to `vault` to select this backend explicitly (optional when it is the only backend configured). | | `VAULT_ADDR` | Vault address, from the standard Vault environment. | | `VAULT_TOKEN` | Vault token, from the standard Vault environment. | The Transit key must be created with `derived=true`. Per-identity binding relies on Vault deriving a per-context key, so a non-derived key will not enforce it. ## Reading and rotation A ciphertext that can't be read under its identity — a wrong or corrupt blob, or a mismatched encryption context — degrades to "reconnect" (the affected integration reads as disconnected until reconnected) rather than returning a wrong value. Store-wide misconfigurations are surfaced, not masked. In particular, if `OMNIGENT_CREDENTIAL_VAULT_KEY` is repointed to a different key, the Vault backend refuses to read credentials written under the old key rather than treating them as per-user disconnects — which would re-encrypt them under the wrong key and overwrite still-recoverable ciphertext. --- ## https://omnigent.ai/docs/deploy/database # Database The Omnigent server needs a database to persist session history, user accounts, and artifacts. Postgres, CockroachDB, and SQLite are supported. All three use the same schema and migrations. ## Postgres (recommended) Postgres is the production choice. It is required if you run more than one server instance. - Provisioned on [Render and Railway](https://omnigent.ai/docs/deploy/overview#cloud-platforms). Render wires it automatically; on Railway you add a Postgres plugin and set a `DATABASE_URL` reference variable (`${{Postgres.DATABASE_URL}}`) on the app service yourself. - For other platforms, bring your own. The fastest option is [Neon](https://pg.new): create a database and set `DATABASE_URL`. - Any `postgres://` or `postgresql://` URL works. The entrypoint normalizes it automatically. ``` DATABASE_URL=postgresql://user:pass@host:5432/omnigent ``` ### Postgres driver The PostgreSQL driver (`psycopg`) ships in an opt-in extra rather than the base install. Install it whenever you point a self-installed Omnigent CLI or server at a Postgres database: ``` pip install 'omnigent[postgres]' ``` For a `uv tool` install, add the driver with: ``` uv tool install omnigent --with 'psycopg[binary]' ``` If the driver is missing when the database URI is Postgres, Omnigent fails to start with an actionable error naming these install commands. Point the CLI or local server at Postgres by setting `OMNIGENT_DATABASE_URI` (or passing `--database-uri`) to a `postgresql+psycopg://` URL: ``` OMNIGENT_DATABASE_URI=postgresql+psycopg://user:pass@host:5432/omnigent ``` Bare `postgres://` and `postgresql://` URLs are normalized to the psycopg 3 dialect automatically; when passing a URI directly, prefer the explicit `postgresql+psycopg://` scheme. The legacy `psycopg2` dialect is not installed — switch the scheme to `postgresql+psycopg://`, or install `psycopg2-binary` yourself to keep it. ## CockroachDB CockroachDB is supported through its SQLAlchemy dialect. Install the optional driver before pointing Omnigent at a CockroachDB connection: ``` pip install 'omnigent[cockroachdb]' ``` If the driver is missing when the database URI is CockroachDB, Omnigent fails to start with an actionable error naming this install command. Either URI form works. Omnigent normalizes the short form to psycopg 3: ``` cockroachdb://user:password@host:26257/database cockroachdb+psycopg://user:password@host:26257/database ``` When passing a URI directly, prefer the explicit `cockroachdb+psycopg://` scheme. ### Compatibility Omnigent requires CockroachDB **v23.2.28 or newer**. CockroachDB v23.2 provides `READ COMMITTED` as an opt-in preview. Enable it on the cluster before starting Omnigent: ```sql SET CLUSTER SETTING sql.txn.read_committed_isolation.enabled = true; ``` Omnigent checks the effective isolation level at startup and fails with an actionable error instead of silently running v23.2 transactions as `SERIALIZABLE`. The engine runs at `READ COMMITTED`, preserves explicit row locks, and replays database-only transaction callbacks after serialization (SQLSTATE `40001`) errors; it never retries constraint failures or external side effects. ### First boot Start CockroachDB against an **empty** database. On first boot Omnigent creates the current schema directly and stamps the current Alembic revision rather than running the historical PostgreSQL migration chain. A database left at a partial revision by an earlier attempt is unsupported and must be replaced with a new empty database. Existing CockroachDB databases created by a supported Omnigent release use normal Alembic upgrades for subsequent migrations. ## SQLite SQLite is the zero-dependency option for demos and single-instance deploys. ``` DATABASE_URL=sqlite:////data/artifacts/chat.db ``` The `.db` file lives on the platform's persistent disk or volume. ## Comparison | | Postgres | CockroachDB | SQLite | | --------------- | --------------------------- | ------------------------------ | ------------------ | | Multi-instance | Yes | Yes | No | | Managed backups | Yes (with managed Postgres) | Yes (with managed CockroachDB) | No | | Setup required | Database provisioning | Empty database provisioning | None | | Best for | Production | Distributed / multi-region | Demos, single-user | **Warning:** SQLite stores its `.db` file on local disk, so it needs a persistent disk or volume. On platforms with ephemeral disk, such as Hugging Face Spaces (persistent storage there is a paid add-on), the database is wiped on every restart; use Postgres there. See the [deployment overview](https://omnigent.ai/docs/deploy/overview) page for platform specifics. ## First boot First boot against a remote Postgres runs migrations over the network. This takes approximately one minute on services like Neon. Subsequent boots are fast. Make sure your platform's healthcheck grace period tolerates the initial migration time. A 120-second grace period is typically sufficient. ## Connection pool Server databases default to 200 pooled connections, 20 overflow connections, and a 10-second pool wait, matching the server's worker concurrency. These global settings override the defaults for Postgres, MySQL, and CockroachDB: - `OMNIGENT_DB_POOL_SIZE` - `OMNIGENT_DB_MAX_OVERFLOW` - `OMNIGENT_DB_POOL_TIMEOUT` Blank values use the defaults. `OMNIGENT_DB_POOL_SIZE=0` removes the base-pool limit, `OMNIGENT_DB_MAX_OVERFLOW=-1` permits unlimited overflow, and the pool timeout accepts non-negative fractional seconds. --- ## https://omnigent.ai/docs/deploy/databricks-connect # Connect Databricks Omnigent supports **per-user Databricks connect**: once a server admin registers a Databricks OAuth app, a signed-in user can go to **Settings → Sandbox Integrations → Connect Databricks**, supply their workspace URL, and authorize their Databricks workspace. Their managed sandboxes then reach the workspace's **AI Gateway** (MCP + model serving) as them, using a short-lived, per-user OAuth token instead of a shared personal access token. This mirrors [Connect GitHub](https://omnigent.ai/docs/deploy/github-connect): same short-lived, user-bound token model, same **Settings → Sandbox Integrations** entry point. Because Databricks is multi-workspace, the user supplies their workspace host at connect time. This feature is only wired when the [Credential Store](https://omnigent.ai/docs/deploy/credential-store) is configured (`OMNIGENT_CREDENTIAL_*` / KMS). Tokens are persisted encrypted at rest by the credential store, reusing the existing connection storage — no new table or migration. Without a KMS key (`OMNIGENT_CREDENTIAL_KMS_KEY_ID`), Databricks connect logs an error and stays disabled. ## How it works - **Connect (user OAuth).** The server signs a short-lived, user-bound `state` and redirects the user to their workspace's OAuth authorize screen using **OAuth U2M** (authorization-code + PKCE). The workspace redirects back to `…/v1/connections/databricks/callback`, which exchanges the code for a per-user access token (plus a refresh token, from `offline_access`) and stores it encrypted in the credential store. - **Per-workspace host.** Databricks is multi-workspace, so the OAuth endpoints are workspace-relative (`https:///oidc/v1/authorize` and `/oidc/v1/token`). The workspace host the user enters is carried in the signed state and stored in the connection metadata. - **Use (in the sandbox).** On sandbox launch the host writes a `~/.databrickscfg` profile pointing at the owner's per-user credential broker (workspace host only — the token is never persisted on sandbox disk). The command-based harnesses (codex, claude, pi) mint a fresh token on demand from the broker and refresh as it nears expiry, so long sessions stay authenticated to the AI Gateway. ## Register a Databricks OAuth app A Databricks **account admin** must register an OAuth custom app integration to obtain a client id / secret and register the callback URL. This is an account action and cannot be done from Omnigent (the same way the GitHub App is registered externally). ``` databricks account custom-app-integration create --json '{ "name": "omnigent-connect", "redirect_urls": ["https:///v1/connections/databricks/callback"], "scopes": ["all-apis", "offline_access"], "confidential": true }' ``` - The `redirect_urls` entry **must** equal `OMNIGENT_DATABRICKS_REDIRECT_URI` (or the value Omnigent derives from `OMNIGENT_DOMAIN`, which is exactly this shape). - `offline_access` is required to receive a refresh token for server-side refresh. `all-apis` covers the AI Gateway surface — narrow it once you confirm the specific scopes your gateway needs. - `confidential: true` yields a client secret, which Omnigent requires. ## Wire it into the server Set these on the **server**. The feature enables itself once a client id + secret + a resolvable redirect URI are present (and the credential store is configured): ``` # Required. OMNIGENT_DATABRICKS_CLIENT_ID= OMNIGENT_DATABRICKS_CLIENT_SECRET= # Redirect URI — set explicitly, OR let Omnigent derive it from OMNIGENT_DOMAIN # as https://$OMNIGENT_DOMAIN/v1/connections/databricks/callback (same shape). OMNIGENT_DATABRICKS_REDIRECT_URI=https:///v1/connections/databricks/callback # Optional. OMNIGENT_DATABRICKS_SCOPES="all-apis offline_access" # default shown OMNIGENT_DATABRICKS_ALLOWED_HOST_SUFFIXES=.databricks.com,.azuredatabricks.net # default shown ``` | Variable | Required | Purpose | | ------------------------------------------- | -------- | ---------------------------------------------------------------------------------------- | | `OMNIGENT_DATABRICKS_CLIENT_ID` | Yes | OAuth client id (the account custom-app-integration id). | | `OMNIGENT_DATABRICKS_CLIENT_SECRET` | Yes | App client secret; also signs the OAuth state and derives the PKCE verifier. | | `OMNIGENT_DATABRICKS_REDIRECT_URI` | Yes\* | OAuth callback URL. \*Derived from `OMNIGENT_DOMAIN` if unset. | | `OMNIGENT_DATABRICKS_SCOPES` | No | Space-separated scopes to request. Defaults to `all-apis offline_access`. | | `OMNIGENT_DATABRICKS_ALLOWED_HOST_SUFFIXES` | No | Comma-separated, dot-prefixed workspace host suffixes users may connect. Defaults below. | The client secret is POSTed to the user-supplied workspace host, so Omnigent gates that host to Databricks domains: by default `.databricks.com` and `.azuredatabricks.net`. Operators on a private or government region can extend the set with `OMNIGENT_DATABRICKS_ALLOWED_HOST_SUFFIXES`. Restart the server. If the client id and secret are set but no redirect URI can be resolved (neither `OMNIGENT_DATABRICKS_REDIRECT_URI` nor `OMNIGENT_DOMAIN`), the feature logs a warning and stays disabled. Both the CLI (`omnigent server`) and the Docker image read the same env vars. ## Verify 1. Sign in to Omnigent as a real user and open **Settings → Sandbox Integrations**. The **Connect Databricks** control appears — the nav item only shows when a connection provider is wired (surfaced via `enabled_connections` in `/v1/info`). 2. Enter your workspace URL (e.g. `your-workspace.cloud.databricks.com`) and click **Connect Databricks** → the workspace authorize screen → back to Omnigent. The panel now shows **Connected to `` as ``**. **Disconnect** removes the stored token. 3. Start a sandbox session and confirm the agent's Databricks calls (model serving / MCP) route through the workspace's AI Gateway as you. --- ## https://omnigent.ai/docs/deploy/github-connect # Connect GitHub Omnigent supports **per-user GitHub connect**: once a server admin wires a GitHub App, a signed-in user can go to **Settings → Sandbox Integrations → Connect GitHub**, and their managed sandboxes then authenticate `git` and `gh` as them — cloning private repos, pushing branches, and opening PRs — using a short-lived, per-user token instead of a shared personal access token. This feature is only wired when the [Credential Store](https://omnigent.ai/docs/deploy/credential-store) is configured (`OMNIGENT_CREDENTIAL_*` / KMS). Tokens are persisted encrypted at rest by the credential store; without it, GitHub connect logs a warning and stays disabled. ## How it works - **Connect (user OAuth).** The server signs a short-lived, user-bound `state` and redirects the user to GitHub's authorize screen. GitHub redirects back to `…/v1/connections/github/callback`, which exchanges the code for a **user-to-server** access token (plus a refresh token) and stores it encrypted in the credential store. - **Use (in the sandbox).** On sandbox launch the server vends that user's fresh token to the runner, which materializes `git` / `gh` credentials and injects the user's public SSH keys. A refresh token keeps long sessions authenticated. - **App, not OAuth App or PAT.** A GitHub App is an org-ownable, least-privilege identity. Per-repo content access requires the App to be **installed** on the account or org that owns the repo, so access is auditable and revocable per install, with no long-lived personal secret to leak. **The one gotcha:** user OAuth alone lets Omnigent _list_ the repos a user can see, but reading a **private** repo's contents or branches requires the App to be **installed** on that repo's owner. A connected-but-not-installed App returns `404` on private repo content. ## Register the GitHub App Register a new App under **Settings → Developer settings → GitHub Apps → New GitHub App** (`https://github.com/settings/apps/new`; for an org use `https://github.com/organizations//settings/apps/new`). 1. **Name / Homepage URL.** Any unique name (e.g. `omnigent-connect`); set the homepage to your Omnigent origin. 2. **Callback URL.** Set it to `https:///v1/connections/github/callback`. This **must** equal `OMNIGENT_GITHUB_APP_REDIRECT_URI` (or the value Omnigent derives from `OMNIGENT_DOMAIN`, which is exactly this shape). 3. **Expire user authorization tokens.** Leave **checked** — Omnigent uses the refresh token to keep long sessions alive. 4. **Request user authorization (OAuth) during installation** and **Enable Device Flow.** Leave both **unchecked**. 5. **Webhook.** Omnigent does not consume webhooks — **uncheck "Active"** and leave the URL blank. 6. **Permissions.** Under **Repository permissions**, grant only what sandboxes need. The user-to-server token inherits exactly these scopes, so this list is the ceiling on what a connected sandbox can do as the user. | Permission | Access | Why | | ----------------- | -------------- | ---------------------------------------------------------------------------------------- | | **Contents** | Read and write | Clone repos and push branches for PRs. Use **Read-only** if sandboxes should only clone. | | **Metadata** | Read-only | Auto-selected; required by GitHub. | | **Pull requests** | Read and write | Open PRs from the sandbox. **Read-only** if you never create PRs. | Leave everything else **No access**. No **Account** or **Organization** permissions are needed. 7. **Where can this App be installed?** Pick **Only on this account** for a single-account or dev instance, or **Any account** if other orgs will install it. Click **Create GitHub App**. ## Collect credentials From the App's **General** page: - **Client ID** — non-secret; maps to `OMNIGENT_GITHUB_APP_CLIENT_ID`. - **Client secret** — click **Generate a new client secret** and copy it once (GitHub only shows it once). Maps to `OMNIGENT_GITHUB_APP_CLIENT_SECRET`. - **App ID** _(optional)_ — the numeric id near the top; maps to `OMNIGENT_GITHUB_APP_ID`. Only needed for app-level JWT calls, not the per-user connect flow. - **Private key** _(optional)_ — **Generate a private key** downloads a `.pem`. Only needed for app-level (installation-token) calls. Maps to `OMNIGENT_GITHUB_APP_PRIVATE_KEY` (PEM contents) or `OMNIGENT_GITHUB_APP_PRIVATE_KEY_PATH` (a file path). Treat the client secret and private key like passwords — never commit them; put them in the same secret store the server already reads. ## Install the App Open the App's **Install App** tab (or `https://github.com/apps//installations/new`) and install it on the account or org whose repositories users will access — **All repositories** or a selected set. This step is **required for private-repo content**: without an installation on the repo's owner, cloning or listing branches of a private repo returns `404` even for a correctly connected user. Set `OMNIGENT_GITHUB_APP_SLUG` to the App's slug (from its URL, `github.com/apps/`) so Omnigent can render the in-product **Install** link. ## Wire it into the server Set these on the **server**. The feature enables itself once a client id + secret + a resolvable redirect URI are present (and the credential store is configured): ``` # Required. OMNIGENT_GITHUB_APP_CLIENT_ID=Iv23li... # App "Client ID" OMNIGENT_GITHUB_APP_CLIENT_SECRET= # from "Generate a new client secret" # Redirect URI — set explicitly, OR let Omnigent derive it from OMNIGENT_DOMAIN # as https://$OMNIGENT_DOMAIN/v1/connections/github/callback (same shape). OMNIGENT_GITHUB_APP_REDIRECT_URI=https:///v1/connections/github/callback # Optional. OMNIGENT_GITHUB_APP_SLUG=omnigent-connect # for the in-product Install link OMNIGENT_GITHUB_APP_ID=1234567 # only for app-level JWT calls OMNIGENT_GITHUB_APP_PRIVATE_KEY_PATH=/etc/omnigent/github-app.pem # or _PRIVATE_KEY= ``` | Variable | Required | Purpose | | -------------------------------------- | -------- | ------------------------------------------------------------------- | | `OMNIGENT_GITHUB_APP_CLIENT_ID` | Yes | App client id used for the user authorization flow. | | `OMNIGENT_GITHUB_APP_CLIENT_SECRET` | Yes | App client secret. | | `OMNIGENT_GITHUB_APP_REDIRECT_URI` | Yes\* | OAuth callback URL. \*Derived from `OMNIGENT_DOMAIN` if unset. | | `OMNIGENT_GITHUB_APP_SLUG` | No | App slug, used to build the in-product install link. | | `OMNIGENT_GITHUB_APP_ID` | No | Numeric App ID; only for app-level JWT calls. | | `OMNIGENT_GITHUB_APP_PRIVATE_KEY` | No | RSA private key PEM contents, for app-level calls. | | `OMNIGENT_GITHUB_APP_PRIVATE_KEY_PATH` | No | Path to the private key PEM file (alternative to the inline value). | Restart the server. If the client id and secret are set but no redirect URI can be resolved (neither `OMNIGENT_GITHUB_APP_REDIRECT_URI` nor `OMNIGENT_DOMAIN`), the feature logs a warning and stays disabled. Both the CLI (`omnigent server`) and the Docker image read the same env vars. ## Verify 1. Sign in to Omnigent as a real user and open **Settings → Sandbox Integrations**. The **Connect GitHub** control appears — the nav item only shows when a connection provider is wired (surfaced via `enabled_connections` in `/v1/info`). 2. Click **Connect GitHub** → GitHub's authorize screen → back to Omnigent. The panel now shows **Connected as ``**. **Disconnect** revokes the stored token. 3. Start a sandbox session on a **private** repo owned by an account where the App is **installed**; confirm the sandbox can `git clone`, push a branch, and (if enabled) open a PR. If step 3 returns `404` on a private repo while the connect flow succeeded, the App is connected but **not installed** on that repo's owner — revisit [Install the App](#install-the-app). --- ## https://omnigent.ai/docs/deploy/overview # Shared Server Everything in the previous sections runs on your laptop. That's the fastest way to get started, but when you want your Omnigent accessible from your phone, shareable with teammates, or running while your laptop sleeps, you need to deploy. This section covers Omnigent's architecture, the options for hosting the server, and the options for hosting the runner. Omnigent has three components: the server, the runner, and the UI. ![Omnigent architecture: CLI agents and custom agents run through a runner (on your machine, Modal, or Daytona), then a server that adds policies and history, reachable from a terminal, the web, native and mobile apps, and a REST API.](https://omnigent.ai/images/architecture.svg) ## Server The **server** is the central coordinator. It manages: - **Session history.** Every conversation, message, and tool call is persisted in a [database](https://omnigent.ai/docs/deploy/database) (Postgres or SQLite). - **Artifacts.** Files, Omnigent bundles, and uploads. - **Catalog.** Registered and built-in Omnigent specs. - **MCP proxy & policies.** Proxies MCP tool calls with server-side policy enforcement. - **Skills.** Skill definitions that Omnigent can load. - **Auth & accounts.** User authentication (built-in accounts or OIDC/SSO). There are a few options for hosting the server on the cloud: ### Docker Compose Deploy the Omnigent server with Docker Compose. The stack includes the server and a Postgres database. ``` cd deploy/docker ./bootstrap.sh # generates DB password + cookie secret into .env docker compose up -d # Omnigent server + Postgres ``` Key variables in `.env` (See [Auth & SSO](https://omnigent.ai/docs/collaborate/auth) for details on multi-user auth): | Variable | Purpose | | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | | `DATABASE_URL` | Postgres connection string | | `OMNIGENT_AUTH_ENABLED` | Enable multi-user auth (default: `1` in Docker) | | `OMNIGENT_OIDC_COOKIE_SECRET` | Session cookie secret | | `OMNIGENT_OIDC_ISSUER` | OIDC issuer URL (enables SSO) | | `OMNIGENT_OIDC_CLIENT_ID` | OIDC client ID | | `OMNIGENT_OIDC_CLIENT_SECRET` | OIDC client secret (omit for [public PKCE clients](https://omnigent.ai/docs/collaborate/auth#public-pkce-clients-and-explicit-endpoints) that opt in) | No admin password is auto-generated. On first boot the server reports `needs_setup`: open the web UI and create the admin account there, or set `OMNIGENT_ACCOUNTS_INIT_ADMIN_PASSWORD` in `.env` to preset it for headless deploys. ### Extra built-in agents Set `OMNIGENT_BUILTIN_AGENT_DIRS` to load extra built-in agents at server startup. In Docker, use colon-separated paths to YAML specs or bundle directories **inside the container**. Place your agents under `deploy/docker/agents/` and create `deploy/docker/docker-compose.override.yaml`, which Compose loads alongside `docker-compose.yaml`: ```yaml services: omnigent: environment: OMNIGENT_BUILTIN_AGENT_DIRS: /agents/my-agent.yaml:/agents/my-bundle volumes: - ./agents:/agents:ro ``` Replace the example paths with your agents, then apply the configuration from `deploy/docker`: ```sh docker compose up -d --force-recreate omnigent ``` After editing a mounted spec, run `docker compose restart omnigent` to reload it. Changes to the paths or mounts require the force-recreate command above. Each agent is available to all users and named after its YAML file stem or bundle directory. Use a distinct name unless you intend to replace an existing built-in. Invalid or missing entries are logged and skipped. ### Cloud platforms Deploy the server to a cloud platform with managed infrastructure. | Platform | Database | Deploy method | | ----------------------- | ------------------ | ------------------------------------------------------------------------------------------ | | **Railway** | Managed Postgres | Import repo from GitHub | | **Render** | Managed Postgres | [One-click deploy](https://render.com/deploy?repo=https://github.com/omnigent-ai/omnigent) | | **Fly.io** | SQLite on volume | `fly deploy` from CLI | | **Hugging Face Spaces** | SQLite (ephemeral) | Docker Space | **Railway & Render.** On Railway, import the repo and add a Postgres plugin. Adding the plugin does **not** wire its `DATABASE_URL` into the app on its own: open the omnigent service's **Variables**, add a `DATABASE_URL` variable, and set it to a reference to the Postgres service (`${{Postgres.DATABASE_URL}}`). Until that reference is set, the deploy errors with `DATABASE_URL is required` on every boot; add it, then redeploy. Render provisions the app and managed Postgres over HTTPS via [one-click deploy](https://render.com/deploy?repo=https://github.com/omnigent-ai/omnigent). Both default to built-in `accounts` auth, so multi-user works out of the box. **Fly.io.** Deploy with `fly deploy` using SQLite on a persistent volume. Configuration files are in `deploy/fly/` in the repo. ``` cd deploy/fly fly deploy ``` The server idles around ~275 MB RSS. Fly's default 256 MB machine will OOM-loop. The `fly.toml` in the repo pins a 1 GB machine. If you changed it, run `fly scale memory 1024`. **Hugging Face Spaces.** Demo-grade Docker Space with SQLite. See `deploy/hf-spaces/` in the repo. **Warning:** On Hugging Face Spaces, disk is ephemeral by default and persistent storage is a paid add-on. Data resets on every restart. Use this for demos only. ### Databricks On a Databricks workspace there are two paths, and most users want the managed one. **Managed (recommended).** [Omnigent on Databricks](https://docs.databricks.com/aws/en/omnigent/) (Beta) is a fully managed service: Databricks operates the Omnigent server for you, already wired to workspace identity, Foundation Models, AI Gateway, and MLflow Tracing. There is no deploy tooling, Lakebase bootstrap, or bundle to maintain. Enable the **Omnigent** preview in your workspace settings and follow the quickstart there. **Self-managed.** Deploy and operate the Omnigent server yourself on Databricks Apps via [Databricks Asset Bundles](https://docs.databricks.com/aws/en/dev-tools/bundles/), backed by Lakebase (Postgres) and Unity Catalog Volumes. Configuration is in `deploy/databricks/` in the repo. Reach for this only when the managed service is not available in your region, or when you need something it does not expose yet — custom YAML policies, bring-your-own provider API keys, or custom egress controls. The first-party **Canvas** extension is disabled by default. To enable it for a specific self-managed app, set the `OMNIGENT_ENABLE_CANVAS=true` deployment environment variable when invoking `deploy/databricks/deploy.py`: ```bash OMNIGENT_ENABLE_CANVAS=true uv run python deploy/databricks/deploy.py \ --app-name ... ``` Leave the variable unset for every other app. `--skip-web-ui` deploys stay API-only and ignore the Canvas opt-in. To ship additional extensions, pass repeatable `--extension-wheel` arguments; every extension wheel must also fit under the 10 MB Databricks Apps source-file cap. ### Kubernetes Deploy the Omnigent server to a Kubernetes cluster with the kustomize manifests in `deploy/kubernetes/` in the repo. The `overlays/sandbox-runners/` overlay additionally turns on the **`kubernetes`** managed sandbox provider (`sandbox.provider: kubernetes`): a `host_type: managed` session spawns a dedicated runner Pod that runs `omnigent host` and dials back to the server. That provider imports a Kubernetes client the base server image omits, so the overlay pulls the official `ghcr.io/omnigent-ai/omnigent-server-kubernetes` image variant — the standard server image plus the `kubernetes` client extra (`OMNIGENT_EXTRAS=kubernetes`). No self-built image is required; the overlay's `images:` block already points at it. ``` kubectl apply -k deploy/kubernetes/overlays/sandbox-runners # then create the omnigent-creds harness Secret (see the overlay README) ``` See the [Kubernetes sandbox configuration reference](https://omnigent.ai/docs/reference/configuration/kubernetes) for Pod-start timeouts, persistent storage, rotation-friendly Secret mounts, and — for the `agent_sandbox` provider — opt-in [native warm pools](https://omnigent.ai/docs/reference/configuration/kubernetes#native-warm-pools-for-agentsandbox-warmpool) that keep spare Pods ready before a session arrives. ### Release features Release features are gated behind the comma-separated `OMNIGENT_FEATURES` variable (on a self-managed Databricks deploy, the `--features` deploy argument). Each feature has a default, and the value overrides those defaults: - `name` enables a feature. - `-name` disables it. An explicit disable wins regardless of order, so appending `-name` works as a kill switch even if `name` is also listed. - Unset or empty uses the defaults. Unknown names, including unknown `-name` overrides, fail startup, so a typo can't silently change what's rolled out. ``` OMNIGENT_FEATURES=usage_page,harness_install,-claude_osc_status ``` The server resolves the set once at startup and publishes its frontend-visible features in `GET /v1/info` under `features`. Redeploy or restart every replica after you change the value, then reload the web app. To roll a feature back, add `-feature_name` and redeploy the same image. Most release features (such as `usage_page`, `harness_install`, and [`canvas`](https://omnigent.ai/docs/interact/web-ui#canvas)) are off by default. Runner features are resolved by each runner instead of the server: | Feature | Default | Applies to | Effect | | ------------------- | ------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `claude_osc_status` | On | Host / runner | `claude-native` sessions take their working, blocked, and idle status from Claude Code's OSC 7501 terminal reports, falling back to Claude's session status file and then pane activity. Not listed in `/v1/info`. | The OSC path needs Claude Code 2.1.295 or later. Older CLIs use the fallback. Setting `CLAUDE_CODE_DISABLE_TERMINAL_TITLE` also suppresses these status reports. Runner features are read from the **host's** environment, not the server's. `omni host` forwards `OMNIGENT_FEATURES` to the runners it starts, including when it runs as the CLI's background daemon, so changing a remote server's environment does not change a separately running host. To turn a runner feature off (for example `OMNIGENT_FEATURES=-claude_osc_status`), set it where the host runs and restart the host and its runners. Then launch fresh terminals, because existing terminals keep the configuration they launched with. ### Serving under a subpath By default the server and web UI are served from the origin root (`/`). To serve them under a path prefix instead — behind code-server's port proxy at `https:///proxy/6767/`, or an nginx/Traefik `location /omnigent/` block — set the base path. The environment variable and the CLI flag are equivalent: ``` OMNIGENT_WEB_BASE_PATH=/proxy/6767 omni server # or omni server --base-path /proxy/6767 ``` The web UI then prefixes its API, SSE, WebSocket, and asset URLs with that path, and the server accepts requests **whether or not** the proxy forwards the prefix, so one value covers both kinds of proxy: - **Prefix-stripping proxies** (code-server's `/proxy//`) forward `/v1/...` — the server serves it as-is. - **Non-rewriting proxies** (code-server's `/absproxy//`, or an nginx `location` block without a rewrite) forward the full `/proxy/6767/v1/...` — the server strips the configured prefix before routing. Server-issued redirects stay under the prefix too: login and logout, magic-link sign-in, the device-code consent page used by `omni login`, and the GitHub / Databricks connection callbacks all return to the prefixed app instead of the origin root. **Value rules.** Use a leading slash and no trailing slash (`/proxy/6767`); a missing leading slash is added and a trailing slash is dropped. Unset, empty, or `/` means a root deployment (unchanged behavior). The server refuses to start with a value that: - starts with `//`, contains characters outside letters, digits, and `-._~/` (including percent-encoding), or has a `.` / `..` segment; - begins with a reserved API segment — `api`, `assets`, `auth`, `docs`, `health`, `oauth`, `openapi.json`, `redoc`, `v1`, `.well-known`; - begins with one of the web UI's own top-level routes — `approve`, `c`, `canvas`, `extensions`, `inbox`, `login`, `members`, `policies`, `register`, `settings`, `tasks`, `usage`. **Auth behind a subpath.** For built-in accounts, also set `OMNIGENT_ACCOUNTS_BASE_URL` to the full public URL including the prefix (e.g. `https:///proxy/6767`) so login and invite redirects and cookies resolve correctly. For [OIDC](https://omnigent.ai/docs/collaborate/auth#single-sign-on-oidc), set `OMNIGENT_OIDC_REDIRECT_URI` to the full public callback URL including the prefix (e.g. `https:///proxy/6767/auth/callback`). **Local managed server.** `omni server --background --base-path /proxy/6767` respawns the detached local server under that prefix. The prefix is remembered: later commands that don't set a base path (a plain `omni run`, `connect`, …) reuse the running server as-is rather than restarting it at the root, and only an explicitly different value triggers a respawn. Browser links the CLI opens or prints for that local server (the auto-opened web UI and `/c/` session links) include the prefix; links to a remote `--server` are never rewritten. ## Runner The **runner** is the per-session process that executes Omnigent loops. It manages the harness (Claude Code, Codex, Claude SDK, etc.), runs tools, and streams events back to the server over WebSocket. The server starts runners on a **host**: a machine you register with the server. By default, the host is your laptop. Register it with: ``` omni login // if auth is enabled omni host ``` This is why your local Claude Code or Codex installation "just works." Runners started on your laptop have direct access to your machine's tools, files, and credentials. However, moving the runner to a [cloud sandbox host](https://omnigent.ai/docs/deploy/cloud-sandbox-host) gives you: - **No laptop dependency.** Your Omnigent runs even when your machine is off. - **Cloud-native tooling.** Your Omnigent can access cloud resources directly. - **Isolation.** Each Omnigent runs in its own container, separate from your local environment. - **Scalability.** Run many Omnigents in parallel without taxing your machine. We currently support the [Databricks](https://docs.databricks.com/aws/en/compute/serverless/sandbox), [Modal](https://modal.com), [Daytona](https://www.daytona.io), and [Blaxel](https://blaxel.ai) platforms, with more integrations on the way. Check out the [Cloud Sandbox Host](https://omnigent.ai/docs/deploy/cloud-sandbox-host) page for more detailed setup instructions. ## UI The [web UI](https://omnigent.ai/docs/interact/web-ui), [terminal UI](https://omnigent.ai/docs/interact/terminal), and [mobile UI](https://omnigent.ai/docs/interact/mobile) all talk to the server. They never talk to the runner directly. This means: - If the server is on your laptop, UI access is local only - If the server is deployed to the cloud, any device can reach it, including your phone. Notably, cloud-hosted servers also allow you to collaborate with other users on the same server. ## Collaboration Once the server is cloud-hosted, Omnigent is multi-user: share a live session with a link, let a teammate co-drive your Omnigent, or fork a conversation so someone can continue independently. This has moved to its own page — see [Pair Programming](https://omnigent.ai/docs/collaborate) for co-drive, sharing with Read/Edit permissions, and forking. --- ## https://omnigent.ai/docs/deploy/telemetry # Usage Telemetry Starting with **v0.6.0**, the Omnigent server collects a small amount of _anonymous usage data_ to help us understand whether Omnigent is working correctly and which features and configuration options are being used. Telemetry is best-effort: it never blocks, delays, or disrupts your server, and any error while collecting or sending data is silently ignored. It does not collect your personal data, prompts, message content, tool arguments, file contents, or credentials. ## Turning telemetry off Any one of the following opts you out: | Method | Scope | | ----------------------------------------------- | -------------- | | `OMNIGENT_ANALYTICS=0` | Server process | | `DO_NOT_TRACK=1` | Server process | | `telemetry: false` in `~/.omnigent/config.yaml` | Server process | For example, to run a server with telemetry off: ``` OMNIGENT_ANALYTICS=0 omnigent server ``` Or set it once in your config file: ```yaml # ~/.omnigent/config.yaml telemetry: false ``` When a **host** machine has opted out via any of the methods above, it signals that to the server during the tunnel handshake. The server honors this on a best-effort basis by skipping telemetry for sessions on that host. ## How it works Telemetry runs on a background thread and is initialised when the server starts. On startup the client fetches a remote configuration that carries the ingestion endpoint and kill-switches (a global disable flag, a per-event disable list, an OS exclusion list, and a rollout percentage). If that config cannot be fetched, or it signals that telemetry is disabled, the client stops itself and drops any pending events — so Omnigent can turn collection off remotely without a new release. --- ## https://omnigent.ai/docs/policies/builtin # Builtin Policies Omnigent ships with policies for common guardrails, organized into two categories: **Safety** and **Cost Control**. Your Omnigent can apply any of these by name when you [ask it to add a policy](https://omnigent.ai/docs/policies/overview#adding-a-policy), or you can reference them in YAML by their full path in the `handler` field. All builtin policies live under `omnigent.policies.builtins`. ## Safety | Policy | What it does | Parameters | | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ask_on_os_tools` | ASKs before any file or shell operation. | None | | `block_skills` | Prevents specific skills from loading. | `blocked` (string[], required) | | `block_working_dir_changes` | Blocks shell commands that change the working directory. | `block_cd` (bool), `block_worktree` (bool), `allowed_dirs` (string[]), `action` (`"deny"` or `"ask"`) | | `cel_policy` | Write custom policy logic using CEL (Common Expression Language), a safe, non-Turing-complete expression language. The expression returns a map with `result` (`"DENY"`, `"ASK"`, or `"ALLOW"`) plus optional `reason` and `state_updates` keys — see [Custom Policies](https://omnigent.ai/docs/policies/custom#reference-stateupdateentry). | `expression` (CEL expression string), `reason` (fallback deny/ask message) | | `deny_pii_in_llm_request` | Scans outgoing messages for PII and blocks or flags them. | `pii_types` (string[]), `action` (`"DENY"` or `"ASK"`) | | `detect_thrashing` | Detects when an agent is failing repeatedly by tracking tool-result outcomes in a rolling window. Fires when consecutive errors cross a threshold or the window error rate is too high. Error detection is heuristic — no server LLM required. | `consecutive_threshold` (int, default `5`; `0` disables), `window` (int, default `10`), `window_error_rate` (number `0.0`–`1.0`, default `0.8`; `0` disables), `action` (`"ASK"` or `"DENY"`, default `"ASK"`) | | `detect_loop` | ASKs when the agent is stuck retrying the same tool call. Hashes each `(tool, arguments)` pair and, when an identical call repeats `threshold` times within a sliding `window` of recent calls, routes it to human approval. Catches a token-waste pattern `max_tool_calls_per_session` can't see, since that policy only counts total calls. | `window` (int, default `10`, min `1`), `threshold` (int, default `3`, min `1`) | | `enforce_sandbox` | Forces a sandbox configuration on agent start. | `sandbox_type`, `allow_network`, `write_paths` (string entries, or `{path, copy_on_write}` objects for [disposable copy-on-write writes](https://omnigent.ai/docs/reference/configuration/os-sandbox#disposable-copy-on-write-writes-linux)), `read_paths` | | `gcalendar_policy` | Controls Google Calendar. Defaults to read-only. | None | | `gdrive_policy` | Controls Google Drive, Docs, Sheets, and Slides access. Writes restricted to agent-created files by default. Optionally enforces Bell-LaPadula "no write-down": once the session reads a confidential file, its writes are confined to that set. | `read_all`, `allow_create`, `write_files`, `read_files`, `comment_files`, `confidential_files` (string[]), `write_down_action` (`"DENY"` or `"ASK"`, default `"DENY"`) | | `github_policy` | Controls GitHub read/write access across MCP tools and shell commands. Irreversible destructive operations (deletes) are denied by default even on allowed repos, unless `allow_destructive` is set. `git push` force flags (`--force`, `-f`, `--force-with-lease`, `--force-if-includes`), bundled short flags containing `f` (e.g. `-uf`), and `+refspec` force prefixes are denied by default regardless of the repo/branch allowlists, unless `deny_force_push` is set to `false`. | `read_all`, `write_repos`, `write_branches`, `allow_destructive` (bool, default `false`), `deny_force_push` (bool, default `true`) | | `gmail_policy` | Controls Gmail. Defaults to read + draft, no send. | `allow_read`, `allow_send`, `allow_drafts` | | `intent_based_authorization` | Records the first user message as the session's intent, then ASKs before any tool call that has no plausible connection to that intent. Requires an `llm:` config block; fails open when none is available. | None | | `max_tool_calls_per_session` | DENYs after a total tool-call limit is reached. | `limit` (int, default `100`) | | `prompt_policy` | Evaluate policy decisions using an LLM. The policy sends the event context to a model and interprets the response as ALLOW/ASK/DENY. Useful for nuanced decisions that can't be expressed as static rules. | `prompt` (system instructions for the evaluator model) | | `risk_score_policy` | Accumulate a risk score from tool calls and sensitive data labels. Escalates guarded tools to ASK or DENY once the score exceeds a threshold. | `threshold` (int), `tool_points` (object mapping tool names to points), `sensitive_labels` (object mapping labels to points), `guarded_tools` (string[]), `escalate_action` (`"ASK"` or `"DENY"`) | | `blast_radius` | Classifies shell commands as safe (ALLOW), recoverable-but-risky (`risky_action`), or catastrophic (always DENY, e.g. `rm -rf /` or force-push). Covers the shell tool across Omnigent (`sys_os_shell`), Claude/Codex (`Bash`), Cursor, Pi, Hermes, and Goose. | `gate_pushes` (bool, default `true`), `risky_action` (`"ASK"` default or `"DENY"`), `deny_reason` | | `spawn_bounds` | Limits the number of sub-agent dispatches per turn to prevent runaway fan-out. | `max_dispatches_per_turn` (int, default `5`), `dispatch_tools` | | `headless_subagent_purpose_guard` | Requires every sub-agent dispatch to declare a `purpose` (`implement`, `review`, `explore`, `search`). | `allowed_purposes` (string[]), `deny_reason` | | `worktree_guard` | Blocks file writes outside the worker's git worktree to prevent cross-branch contamination. | `allowed_root` (string, default `.worktrees`), `deny_reason` | | `read_only_os` | Denies every file-mutating tool so a report-only agent can read and run shell but never change code. | `deny_reason` | ### Always-on approval guards Two safety guards are appended to **every** session's policy set automatically. You don't declare them, they take no parameters, and they can't be removed — they are present even when no policies are configured at any level, and sub-agent sessions get them too. They run after any session, agent, and server-wide policies you have declared. | Guard | What it does | | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ask_on_add_policy` | ASKs before `sys_add_policy`, so an agent cannot silently install a session policy. This is the approval step described in [Adding a policy](https://omnigent.ai/docs/policies/overview#adding-a-policy). | | `ask_on_scheduled_task_change` | ASKs before `sys_scheduled_task_create` and `sys_scheduled_task_update`, so an agent cannot persist or change unattended work without consent — including when the call omits fields and inherits defaults. `sys_scheduled_task_list` and `sys_scheduled_task_delete` are not gated. See [Scheduled Tasks](https://omnigent.ai/docs/build/scheduled-tasks#approval-for-create-and-update). | Declining either prompt denies that tool call; accepting allows it to proceed. ## Cost Control | Policy | What it does | Parameters | | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | | `cost_budget` | Tracks cumulative LLM spend per session. ASKs at soft thresholds, blocks expensive models at the hard limit. | `max_cost_usd` (required), `ask_thresholds_usd`, `expensive_models` | | `detect_task_switch` | Uses the server-level LLM to detect when a user starts a new unrelated task and nudges starting a fresh session. | `min_turns` (int), `history_window` (int), `action` (`"ASK"` or `"DENY"`, default `"ASK"`), `classification_prompt` (string) | | `deny_trivial_to_expensive_model` | Classifies messages as trivial or complex. Routes trivial tasks away from expensive models. | `expensive_models` (string[], required), `classification_prompt` (string) | | `subagent_cost_budget` | Gates a sub-agent on its own subtree LLM spend. Blocks expensive models at the hard limit, ASKs at soft thresholds. Attach to a child via `sys_session_send`'s `cost_budget` argument. | `max_cost_usd`, `ask_thresholds_usd`, `expensive_models` | | `user_daily_cost_budget` | Same as `cost_budget`, but enforced per-user daily across all sessions. | `max_cost_usd` (required), `ask_thresholds_usd`, `expensive_models` | | `user_period_cost_budget` | Same as `user_daily_cost_budget`, but for a configurable UTC period — `day`, `week`, `month`, `quarter`, or `year`. Sums the user's daily spend across all their sessions for the current period; soft-checkpoint approvals are remembered per user for the rest of the period. | `period` (required), `max_cost_usd` (required), `ask_thresholds_usd`, `expensive_models` | ### Usage examples #### Confine confidential Google Drive data `gdrive_policy` can layer Bell-LaPadula's classic "no write-down" rule on top of its access controls. You declare a set of confidential documents up front with `confidential_files` (file IDs or Google URLs). The rule engages only once the session **reads** one of those files: from then on, its writes are confined to the confidential set. A write to — or creation of — any other file would move confidential data into a less-protected place, so it is blocked. ```yaml # Omnigent config (policies block) policies: confidential_containment: type: function handler: omnigent.policies.builtins.google.gdrive_policy factory_params: # The confidential compartment: file IDs or Google URLs. confidential_files: - "1ConfidentialStrategyDocID" # Allow creating new files, so a create can be shown being gated as a # write-down rather than blocked outright by the base access rules. allow_create: true # DENY (hard block) or ASK (require human approval) on a write-down. write_down_action: DENY ``` Because the confidential set is declared explicitly rather than inferred from a per-document classification label, the rule works on any Google Drive tenant. #### Catch a stuck agent `detect_thrashing` watches `tool_result` events and flags an agent that is failing repeatedly — retrying the same broken command, looping on a permission error, or otherwise making no progress. Unlike [`detect_task_switch`](#keep-sessions-focused), it needs **no server LLM**: error detection is heuristic (common error prefixes like `Error:`, `Traceback (most recent call last)`, `Permission denied`, and JSON payloads carrying an `"error"` key). It keeps a rolling window of recent tool-result outcomes (error or success) in session state and fires when either condition is met: - the last `consecutive_threshold` results are all errors, **or** - the error rate within the last `window` results reaches or exceeds `window_error_rate`. On detection it returns the configured `action` with a message telling the user the agent appears stuck. The window is **not** reset on firing — a thrashing agent tends to keep failing, so the policy keeps firing until the user intervenes or the agent recovers naturally (successful results push old errors out of the window). ```yaml # Omnigent config (policies block) policies: catch_stuck_agent: type: function handler: omnigent.policies.builtins.context.detect_thrashing factory_params: consecutive_threshold: 5 # fire after 5 errors in a row (0 disables) window: 10 # rolling window for the rate check window_error_rate: 0.8 # fire at ≥80% errors in the window (0 disables) action: ASK # ASK escalates to the user; DENY blocks the next result ``` Both checks are independent — set `consecutive_threshold: 0` to rely purely on the window error rate, or `window_error_rate: 0` to rely purely on the consecutive-error run. #### Track custom state from a CEL expression `cel_policy` evaluates a CEL (Common Expression Language) expression against every policy event. The expression must return a map with a `result` key (`"DENY"`, `"ASK"`, or `"ALLOW"`) and may add an optional `reason`. A non-map return (or a map without a valid `result`) abstains, passing to the next policy. Because CEL is non-Turing-complete and side-effect-free, the expression itself can't mutate anything. To let a CEL policy accumulate its own [session state](https://omnigent.ai/docs/policies/overview#why-contextual) across turns, the returned map may also include a `state_updates` key. It must be a list of maps, each shaped: ``` { "key": string, "action": "set" | "increment" | "delete" | "append", "value": any } ``` The `action` values behave as follows, and `value` is required for `set`, `increment`, and `append` and ignored for `delete`: | Action | Effect | | ----------- | ----------------------------------------------------- | | `set` | Store `value` at `key`, replacing any existing value. | | `increment` | Add `value` to the number stored at `key`. | | `append` | Append `value` to the list stored at `key`. | | `delete` | Remove `key` from session state (`value` is ignored). | For example, this policy allows every tool call while counting them and recording the last tool name in session state: ```yaml # Omnigent config (policies block) policies: count_tool_calls: type: function handler: omnigent.policies.builtins.cel.cel_policy factory_params: expression: | event.type == "tool_call" ? { "result": "ALLOW", "state_updates": [ {"key": "call_count", "action": "increment", "value": 1}, {"key": "last_tool", "action": "set", "value": event.data.name} ] } : {"result": "ALLOW"} ``` State written this way is shared session state, so a later policy (or a later evaluation of the same CEL expression) can read it back and branch on it, just like the stateful builtin policies do. Keys that begin with `_policy_` are reserved for the runtime, and a CEL expression can't change them. If a `state_updates` entry targets a reserved key, the event is denied and none of that result's state updates are applied. See [StateUpdateEntry](https://omnigent.ai/docs/policies/custom#reference-stateupdateentry) for the full list of reserved keys. #### Make spend visible before you restrict it Start with soft thresholds only. Engineers see when they're running an expensive session and can decide whether the task warrants it. No one gets blocked; habits change on their own. ```yaml # Omnigent config (policies block) policies: session_visibility: type: function handler: omnigent.policies.builtins.cost.cost_budget factory_params: ask_thresholds_usd: [1.0, 5.0] max_cost_usd: 999.0 # effectively no hard cap daily_visibility: type: function handler: omnigent.policies.builtins.cost.user_daily_cost_budget factory_params: ask_thresholds_usd: [10.0, 25.0] max_cost_usd: 999.0 ``` This gives you real data on where spend is concentrated before you decide where to add guardrails. #### Server-wide team policy A reasonable starting point for a team deployment: per-user daily visibility at $25, a soft check at $50. Engineers can still use any model for any task — they just get asked before spending more than $25 in a day. ```yaml # config.yaml policies: daily_budget: type: function function: path: omnigent.policies.builtins.cost.user_daily_cost_budget arguments: ask_thresholds_usd: [25.0, 50.0] max_cost_usd: 100.0 ``` ``` omnigent server -c config.yaml ``` The daily cap exists to surface sessions that are genuinely off the rails, not to penalize productive use. #### Per-user monthly (or weekly, quarterly, yearly) budget A daily cap doesn't catch a user who stays just under it every day. `user_period_cost_budget` applies the same gating as `user_daily_cost_budget` over a longer window: it sums the owner's spend across **all** of their sessions for the current UTC period and gates on the total. ```yaml # config.yaml policies: monthly_budget: type: function function: path: omnigent.policies.builtins.cost.user_period_cost_budget arguments: period: month # day | week | month | quarter | year ask_thresholds_usd: [250.0, 400.0] max_cost_usd: 500.0 # Omit (or pass []) for a true hard stop that blocks every model at # the limit; name tiers for a downgrade gate that only blocks those. expensive_models: [opus] ``` How the period is computed (all in UTC): | `period` | Window | | --------- | -------------------------------------------- | | `day` | The current calendar day | | `week` | ISO week — Monday through the current day | | `month` | The 1st of the current month through today | | `quarter` | The first day of the calendar quarter onward | | `year` | January 1 of the current year onward | Behavior matches the daily policy: - **Soft checkpoints** (`ask_thresholds_usd`) ASK the first time the period's spend crosses each value. An approval is recorded per user, so the same checkpoint won't re-prompt that user again this period — including from a different session. A decline blocks just that turn or tool call. - **Hard limit** (`max_cost_usd`) DENYs the turn (request phase) or each tool call once the period's spend reaches it. With `expensive_models` set, only the named tiers are blocked so the user can `/model` down to a cheaper one; without it, every model is blocked. - The policy abstains when the period's spend is `0.0` or the session has no owner (single-user mode), so it never trips on missing data. `ask_thresholds_usd` values must each be greater than `0` and less than `max_cost_usd`, and `max_cost_usd` must be greater than `0`. Configure **one** `user_period_cost_budget` policy per policy set. Don't combine it with `user_daily_cost_budget`; to cap daily spend, use `period: day`. Stacking several period policies (for example weekly + monthly) isn't supported. #### Keep sessions focused Use this policy to keep sessions focused and avoid wasting tokens on stale context. On each user `request`, it classifies whether the latest message continues the current task or starts a new one, and returns the configured `action` when it detects a task switch. It requires a server `llm:` config and fails open if no LLM client is available. ```yaml # Omnigent config (policies block) policies: keep_context_lean: type: function handler: omnigent.policies.builtins.context.detect_task_switch factory_params: min_turns: 2 history_window: 4 action: ASK ``` --- ## https://omnigent.ai/docs/policies/custom # Custom Policies When the [builtin policies](https://omnigent.ai/docs/policies/builtin) don't cover your use case, you have two options: 1. **`cel_policy`** — write a [CEL](https://cel.dev) expression directly in your config. No code to deploy. Start here. 2. **Python policy** — a full Python function registered on the server. Use this only when your logic requires state, external calls, or anything else CEL's non-Turing-complete expressions cannot express. ## Using `cel_policy` `cel_policy` is a [builtin policy](https://omnigent.ai/docs/policies/builtin) that evaluates a [CEL](https://cel.dev) expression against every event. CEL is a safe, sandboxed expression language — no loops, no I/O, no side effects — which makes it the right default for most custom rules. The expression receives an `event` object with `type` and `data` fields (the same [`PolicyEvent`](#reference-policyevent) every policy sees). Return `true` to deny the action. ### Examples **Deny a specific tool:** ```yaml policies: no_dangerous_tool: type: function handler: omnigent.policies.builtins.safety.cel_policy factory_params: expression: "event.type == 'tool_call' && event.data.name == 'dangerous_tool'" reason: "dangerous_tool is not allowed in this session." ``` **Block writes outside a specific directory:** ```yaml policies: confine_writes: type: function handler: omnigent.policies.builtins.safety.cel_policy factory_params: expression: > event.type == 'tool_call' && event.data.name in ['sys_os_write', 'sys_os_edit'] && !event.data.arguments.path.startsWith('/home/user/project/') reason: "Writes are only allowed inside /home/user/project/." ``` **Track calls with `state_updates`:** Return a `state_updates` list to accumulate counters or flags in session state. The engine applies the mutations after the policy fires, so later events can read the updated values via `event.session_state`. ```yaml policies: track_tool_calls: type: function handler: omnigent.policies.builtins.safety.cel_policy factory_params: expression: > event.type == 'tool_call' ? {"result": "ALLOW", "state_updates": [ {"key": "tool_count", "action": "increment", "value": 1}, {"key": "last_tool", "action": "set", "value": event.data.name} ]} : {"result": "ALLOW"} ``` State persists across the whole session. A companion policy can read `event.session_state.tool_count` to gate on the accumulated total. Use `cel_policy` whenever your rule can be expressed as a boolean over the event fields. You get the same policy lifecycle (ALLOW / ASK / DENY) without writing or deploying any code. ## Python policies Use a Python policy when `cel_policy` isn't enough — for example, when your rule needs accumulated state across calls, a database lookup, or control flow that CEL cannot express. ### 1. Write a policy function A policy is a Python function that receives an event and returns `ALLOW`, `ASK`, `DENY`, or `None` (no opinion): ```python from omnigent.policies.schema import PolicyEvent, PolicyResponse def my_policy(event: PolicyEvent) -> PolicyResponse | None: if event["type"] != "tool_call": return None if event["data"]["name"] == "dangerous_tool": return {"result": "DENY", "reason": "Blocked."} return {"result": "ALLOW"} ``` If your policy needs parameters, use the factory pattern. The function takes config and returns the evaluator: ```python def block_domains(blocked_domains: list[str]) -> callable: blocked = frozenset(d.lower() for d in blocked_domains) def evaluate(event: PolicyEvent) -> PolicyResponse | None: if event["type"] != "tool_call": return None url = event["data"]["arguments"].get("url", "") for domain in blocked: if domain in url.lower(): return {"result": "DENY", "reason": f"Domain {domain} blocked."} return {"result": "ALLOW"} return evaluate ``` **Note:** Omnigent imports policy functions by their dotted path, and it doesn't add the workspace or current directory to the import path. Install the package that contains your policy in **both** the Omnigent CLI environment and the server/runtime environment. When you launch an agent from the CLI, Omnigent checks its function-policy paths first and fails with an error if one can't be imported. That check doesn't import modules from the workspace directory either. ### 2. Register on the server To make your policy discoverable by your Omnigent and visible in the UI, do two things: **Export a `POLICY_REGISTRY`** from your module: ```python # myorg/policies.py POLICY_REGISTRY = [ { "handler": "myorg.policies.block_domains", "kind": "factory", "name": "Block Domains", "description": "Block web access to specific domains.", "params_schema": { "type": "object", "properties": { "blocked_domains": { "type": "array", "items": {"type": "string"}, "description": "Domains to block" } }, "required": ["blocked_domains"] } } ] ``` **Add the module to your server config:** ```yaml # config.yaml policy_modules: - myorg.policies ``` Then start the server with that config: ```bash omnigent server -c config.yaml ``` The `-c` flag is short for `--config`. Once registered, your custom policies appear alongside the builtins. Your Omnigent can select them when you ask it to add a policy in chat, and they show up in the UI settings panel. Agent bundles uploaded over HTTP can only reference registered handlers. Starting in 0.19.0 this applies on local single-user servers too. A bundle whose `type: function` policy names an unregistered handler is rejected with HTTP 400 before the handler is imported. See [Uploaded agents](https://omnigent.ai/docs/build/tools#uploaded-agents). ### 3. Use it Once registered, your custom policy works the same as any builtin. See [Adding a policy](https://omnigent.ai/docs/policies/overview#adding-a-policy) for all the ways to apply it (chat, Omnigent YAML, or server config). ## Reference: PolicyEvent Every policy receives a `PolicyEvent` dict. Use the `type` field to filter which events you care about: | Event type | When it fires | Key `data` fields | | ------------- | ------------------------------------------------- | ---------------------------------------------------------------------------------------- | | `request` | User sends a message | `user_content` (typed text), `attachments` (list of `{filename, content_type, text}`) | | `tool_call` | Omnigent is about to call a tool | `name` (tool name), `arguments` (dict) | | `tool_result` | A tool has returned a result | `result` (tool output); `event["request_data"]` carries the original `tool_call` payload | | `response` | Omnigent is about to deliver an assistant message | The assistant message text | Return `None` for event types you don't handle. For `request` events, text/code attachments (e.g. a CSV) carry their decoded content in `text`. Archives, Office documents, and databases (`.zip`, `.docx`, `.xlsx`, `.pptx`, `.db`, `.sqlite`, `.sqlite3`) are listed with their `filename` and `content_type` but an empty `text`, so a policy can act on their names and types without their contents being scanned as text. Every event also carries: - **`event["target"]`** — tool name on `tool_call` / `tool_result`; `None` otherwise. - **`event["session_state"]`** — read-only dict of per-session key/value pairs accumulated by earlier policies. Mutate it via `state_updates` in the return value, not by modifying this dict. - **`event["context"]`** — metadata about the session (see below). ### EventContext `event["context"]` is a dict with the following fields: | Field | Type | Description | | ---------------------- | ------------- | ------------------------------------------------------------------------------------------ | | `actor.run_as` | `str` | Authenticated user email, e.g. `"alice@example.com"`. Empty string when unknown. | | `actor.client_id` | `str` | OAuth client ID. Empty string when unknown. | | `usage.input_tokens` | `float` | Cumulative input tokens consumed this session. | | `usage.output_tokens` | `float` | Cumulative output tokens consumed this session. | | `usage.total_cost_usd` | `float` | Cumulative LLM cost in USD. `0.0` until the first LLM call or when pricing is unavailable. | | `model` | `str \| None` | Active model, e.g. `"databricks-claude-sonnet-4-6"`. `None` when unknown. | | `harness` | `str \| None` | Active harness, e.g. `"codex-native"`. `None` on web / API paths. | | `labels` | `dict` | Read-only snapshot of the conversation's guardrail labels. | ## Reference: PolicyResponse A policy callable returns `None` (abstain) or a dict with the following fields: | Field | Type | Required | Description | | --------------- | ---------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `result` | `str` | Yes | `"ALLOW"`, `"DENY"`, or `"ASK"` (case-insensitive). | | `reason` | `str` | No | Human-readable explanation. Shown to the user on `ASK`; logged on `DENY`. Optional on `ALLOW`. | | `data` | any | No | Replacement payload. When present on `ALLOW`, the enforcement site substitutes this value for the original event content — useful for returning a PII-redacted version of tool arguments. | | `state_updates` | list | No | Ordered list of session state mutations. Applied on `ALLOW` and `DENY`; withheld on `ASK` pending approval. See [StateUpdateEntry](#reference-stateupdateentry). | | `set_labels` | `dict[str, str]` | No | Label key-value writes. Filtered through the policy's declared `set_labels` allowlist. | Returning `None` is treated as abstain (equivalent to `{"result": "ALLOW"}`). ```python # minimal {"result": "ALLOW"} # full { "result": "DENY", "reason": "Blocked.", "data": , "state_updates": [ {"key": "call_count", "action": "increment", "value": 1}, ], "set_labels": {"integrity": "0"}, } ``` ## Reference: StateUpdateEntry Each entry in `state_updates` is a dict: | Field | Type | Required | Description | | -------- | ----- | ----------------------- | --------------------------------------------------------------------------------- | | `key` | `str` | Yes | The state key to mutate, e.g. `"call_count"`. | | `action` | `str` | Yes | One of `"set"`, `"increment"`, `"delete"`, `"append"`. | | `value` | any | Yes (except `"delete"`) | The operand. Required for `set`, `increment`, and `append`; ignored for `delete`. | The engine applies updates to `event["session_state"]` so subsequent policy evaluations see the new values. State keys must be strings. Some keys are **reserved** for the runtime: every key that begins with `_policy_`, plus `subagent_cost_ask_approved_usd` and `subagent_cost_over_budget_approved_usd`. These hold approval checkpoints and the counters behind builtin policies, and ordinary policies can't freely write them: - A policy may include a reserved key in `state_updates` only on an `ASK` result. The update is applied only after a human approves the ask. - The builtin counters `_policy_tool_call_count`, `_policy_loop_recent_hashes`, and `_policy_spawn_bounds_dispatches` can be written only by their builtin policies (`max_tool_calls_per_session`, `detect_loop`, and `spawn_bounds`). - If a policy breaks either rule, or uses a non-string key, Omnigent treats the result as a policy error and the event is **denied**. ```python # overwrite {"key": "status", "action": "set", "value": "escalated"} # numeric delta (initializes to 0 if absent) {"key": "call_count", "action": "increment", "value": 1} # append to a list (initializes to [] if absent) {"key": "seen_tools", "action": "append", "value": "sys_os_shell"} # remove the key entirely {"key": "temp_flag", "action": "delete"} ``` --- ## https://omnigent.ai/docs/policies/overview # Contextual Policies Policies control what your Omnigent can and cannot do. They intercept every action (tool calls, LLM requests, file operations) and decide in real time whether to **allow**, **ask for approval**, or **deny**. ## Why “contextual”? Most agent frameworks offer static rules: allow this tool, deny that one. Omnigent policies are different. They are **stateful** and **dynamic**: each policy maintains its own state across the entire session and makes decisions based on what has happened so far. This is what enables policies that static systems simply cannot express: - **Cost budgets.** Track cumulative LLM spend across every turn. Warn at $3, block expensive models at $5. The policy remembers what you've spent. - **Rate limiting.** Count tool calls over time and deny after a threshold. The count persists across the session, not per-request. - **Risk scoring.** Accumulate a risk score from sensitive operations (accessing credentials, modifying production data). Escalate to human approval once the score crosses a threshold. - **Model routing.** Classify tasks as trivial or complex and redirect trivial ones away from expensive models, based on conversation context. Every policy evaluation receives the full session context: cumulative cost, tool call history, data classification labels, and custom state you define. This makes Omnigent's policy system fundamentally more powerful than the static security controls built into individual coding agents. ## What you can enforce For example: cap spending, require approval before destructive actions, restrict access to repos or services, block PII, or route models. See [Builtin Policies](https://omnigent.ai/docs/policies/builtin) for the full list. ## How policies work Each policy returns one of three decisions: | Decision | What happens | | --------- | -------------------------------------------------------- | | **ALLOW** | The action proceeds. | | **ASK** | The action pauses until the user approves or rejects it. | | **DENY** | The action is blocked with an error message. | If any policy returns a DENY decision, the action is blocked; otherwise, if any policy returns an ASK decision, the action waits for your approval; otherwise, it proceeds. Post-tool policies cannot undo an action that has already happened. ### Available policies Omnigent provides [builtin policies](https://omnigent.ai/docs/policies/builtin) for common guardrails (cost budgets, approval gates, access controls, PII blocking, model routing). You can also write your own [custom policies](https://omnigent.ai/docs/policies/custom) in Python and register them on the server to make them available: ```yaml # server config (config.yaml) policy_modules: - myorg.policies ``` Start the server with that file: ```bash omnigent server -c config.yaml ``` The `-c` flag is short for `--config`. This makes your custom policies discoverable alongside the builtins. See [Custom Policies](https://omnigent.ai/docs/policies/custom) for how to write and register them. ### Three levels To apply a policy, you pick one (builtin or custom) and add it at one of three levels. Each level has a different scope and priority: | Level | Who | Scope | How to add | Priority | | ------------------- | --------- | ------------------------------------- | ----------------------------------------------- | -------- | | **Session** | End user | Current session only, not persistent | Ask your Omnigent, or use the UI settings panel | First | | **Omnigent config** | Developer | Every session using this Omnigent | `policies` block in the Omnigent YAML | Second | | **Server-wide** | Admin | Every agent and session on the server | Settings page, server config YAML, or REST API | Last | An admin can set a company-wide cost cap, a developer can restrict which repos the Omnigent writes to, and an end user can add extra approval gates. All three levels are enforced simultaneously. ## Adding a policy ### Session level: ask your Omnigent (recommended) The easiest way to add a policy is to ask your Omnigent directly. Describe what you want in plain language: ``` You: Add a policy that asks me before running any shell commands. You: Limit this session to $5 of LLM spend. You: Block access to all GitHub repos except myorg/frontend. ``` Your Omnigent picks the right policy from the available registry (both [builtin](https://omnigent.ai/docs/policies/builtin) and any [custom policies](https://omnigent.ai/docs/policies/custom) registered on the server), configures the parameters, and asks for your approval. Once you approve, the policy takes effect immediately on the next turn of the current session. You can also browse and toggle policies from the **settings panel** in the web UI without typing anything in chat. ### Omnigent config level: declare in YAML For policies that should always apply, declare them in the `policies` block of your Omnigent config. These take effect every time the Omnigent starts. ```yaml policies: approve_file_ops: type: function handler: omnigent.policies.builtins.safety.ask_on_os_tools rate_limit: type: function handler: omnigent.policies.builtins.safety.max_tool_calls_per_session factory_params: limit: 50 ``` Here `rate_limit` caps the total number of tool calls a single session can make (50 in this example), a guard against runaway agent loops and surprise cost. Once the cap is reached, further tool calls are denied for the rest of the session. Policies without parameters use just `handler`. Configurable policies add `factory_params`. Multiple policies are evaluated in the order declared. ### Server-wide level: server config Server-wide policies apply to every session on your deployed server. Add them to a config file and pass it with `-c` (`--config` is the long form): ```yaml policies: session_cost_guard: type: function function: path: omnigent.policies.builtins.cost.cost_budget arguments: ask_thresholds_usd: [1.0] max_cost_usd: 5.0 user_daily_cost_guard: type: function function: path: omnigent.policies.builtins.cost.user_daily_cost_budget arguments: ask_thresholds_usd: [10.0, 25.0] max_cost_usd: 50.0 ``` ```bash omnigent server -c config.yaml ``` Server-wide policies use the `function: {path, arguments}` format (not `handler` / `factory_params`). Restart the server after editing. Admins can also manage server-wide policies without touching config files from the **Settings** page at `/settings/policies`. Changes take effect immediately without a server restart. ![Admin policies settings page](https://omnigent.ai/images/docs/admin-policies.png) Server-wide policies can also be managed at runtime via the REST API: | Method | Endpoint | Description | | -------- | ------------------- | ----------------- | | `POST` | `/v1/policies` | Create a policy | | `GET` | `/v1/policies` | List all policies | | `PATCH` | `/v1/policies/{id}` | Update a policy | | `DELETE` | `/v1/policies/{id}` | Remove a policy | --- ## https://omnigent.ai/docs/omnibox # Omnibox Omnibox is Omnigent's secure OS-level sandbox. It wraps any agent (Claude Code, Codex, or a custom agent) so you can run it unattended, in YOLO mode, without handing it your real credentials, file system, or network. The kernel enforces the rules, not the agent or its prompt: `bubblewrap` and `seccomp` on Linux, Seatbelt (`sandbox-exec`) on macOS. Every process the agent spawns inherits the boundary, so a prompt-injected or misbehaving agent can't opt out. Omnibox combines three protections: - Filesystem isolation - Network isolation - Credential injection ## Filesystem isolation The agent sees only the paths you grant; everything else doesn't exist from inside the sandbox. Even `cwd` is read-only until you opt directories back in, so a runaway agent can't read your SSH keys or write outside the workspace. Broad read grants stay safe: granting `~` doesn't expose `~/.ssh` or `~/.aws/credentials`, because dotfiles are masked unless you allow them. See the [filesystem settings](https://omnigent.ai/docs/reference/configuration/os-sandbox#filesystem) for read and write grants, denied paths, and path expansion behavior. ## Network isolation When you configure `egress_rules`, HTTP(S) traffic goes through a default-deny proxy with an explicit allow-list of methods, hosts, and paths. Data can't leave except to hosts you chose, which shuts down exfiltration even if the agent is prompt-injected. Private IPs and cloud metadata endpoints are blocked by default, so the agent can't reach your internal services. See the [network settings](https://omnigent.ai/docs/reference/configuration/os-sandbox#network) for allowed hosts, methods, paths, and localhost access. ## Credential injection The agent holds a fake placeholder token instead of the real secret. When a request matching your allow-list leaves the sandbox, the proxy swaps in the real credential. The agent can use a credential but never read it. Only the placeholder appears in logs, transcripts, and model context. A leaked token is worthless, since it only works through the proxy, against allowed hosts. See [credential injection](https://omnigent.ai/docs/reference/configuration/os-sandbox#credentials-secretless-proxy) for placeholder mappings, secret sources, and supported authentication flows. ## Related - [OS sandbox configuration](https://omnigent.ai/docs/reference/configuration/os-sandbox): the full reference, covering filesystem grants, egress rules, environment passthrough, and the `bubblewrap`/Seatbelt backends. - [Cloud sandbox host](https://omnigent.ai/docs/deploy/cloud-sandbox-host): controls _where_ the runner executes. Omnibox controls _what it can access_. They compose. - [Contextual policies](https://omnigent.ai/docs/policies/overview): govern behavior at the tool-call level. Omnibox is the hard boundary underneath. --- ## https://omnigent.ai/docs/programmatic # Programmatic Usage Integrate Omnigent into your scripts, applications, and CI pipelines. Start agent sessions, send them work, and return to the same conversations for follow-up tasks. You can also open saved sessions in the Web UI to review their progress and results. - **CLI:** Run a task from the command line and receive the agent's response. - **Python SDK:** Send messages and manage sessions from Python. - **REST API:** Create sessions, read their history, and manage their lifecycle over HTTP. ## Before you start [Install Omnigent](https://omnigent.ai/quickstart/install) and configure the [model credentials](https://omnigent.ai/docs/build/models) your agent needs. The examples use a local single-user server at `http://127.0.0.1:6767`. For a shared server, replace that URL and configure authentication before sending requests. See [Shared Server](https://omnigent.ai/docs/deploy/overview) for deployment setup. Local single-user servers do not require per-request credentials. **Python** Install either the full `omnigent` Python package or just `omnigent-client` for the SDK. Both provide the `omnigent_client` import used below. Use a package version matching your server version. **Authentication for a shared server** Shared servers support both user authentication and optional machine tokens. The credentials you use depend on the server's [authentication setup](https://omnigent.ai/docs/collaborate/auth): - **Accounts or SSO:** Use your signed-in user's session cookie or CLI login bearer token for API requests. These support scheduled tasks as well as sessions, subject to your user's permissions. ```bash omni login "" ``` This prompts for your username and password for built-in accounts, or opens a browser for SSO. It saves credentials for subsequent CLI commands; it does not print a token for use in `curl`. For **built-in accounts**, you can also request a token directly: ```bash ACCESS_TOKEN=$(curl --fail-with-body "/auth/login" \ -H 'Content-Type: application/json' \ -d '{"username": "", "password": ""}' \ | jq -er '.token') ``` Use it in API requests with `-H "Authorization: Bearer $ACCESS_TOKEN"`. This username/password endpoint does not apply to SSO. - **Authentication proxy:** Connect through the proxy, which supplies your identity to Omnigent. Follow your proxy provider's authentication method; Omnigent does not issue a token for this mode. - **Machine tokens:** For unattended scripts, an administrator can enable machine access and provide a client ID and secret. These tokens support agent, host, runner, and session APIs, but cannot access scheduled tasks. ```bash curl --fail-with-body "/oauth/token" \ --data-urlencode grant_type=client_credentials \ --data-urlencode "client_id=" \ --data-urlencode "client_secret=" ``` Include the returned `access_token` in your API requests or Python SDK headers as `Authorization: Bearer `. Tokens expire after one hour by default; run the same command to get a new one. ## Start a session **CLI** From your repository, run an agent with `-p` to send a prompt, print its response, and exit. Use your [agent directory or YAML file](https://omnigent.ai/docs/use/custom-agents) in place of `./my-agent/`: ```bash omni run ./my-agent/ -p "Review the changes in the latest commit" ``` Omnigent starts a local server and runner automatically. Sessions are saved by default; their URL appears on stderr when they start. Open it to follow progress in the Web UI: ```text Omnigent session: http://127.0.0.1:6767/c/conv_abc123 ``` Common options: | Option | When to use it | | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | | `-p ""` / `--prompt ""` | Run a task without an interactive chat; the response goes to stdout. | | `--server ""` | Save the session on a shared server. The agent definition is uploaded, and tools still run on the machine invoking the CLI. | | `--no-session` | Run a disposable local task with no history to view or resume afterward. | | `--model ""` | Override the agent's model. See [Models & Credentials](https://omnigent.ai/docs/build/models). | | `--harness ""` | Override the agent's harness. See [Harnesses](https://omnigent.ai/docs/build/harnesses) for supported configurations. | | `--continue` / `-c` | Continue the most recent conversation for the same agent. | | `--resume ""` | Continue a specific conversation using the ID from its session URL. | These examples use an agent YAML or directory through `omni run`; native terminal harnesses have their own CLI behavior. To run tools on another machine or in a cloud sandbox, use the REST API's host options. **Python** This example creates a native Claude Code session on an existing server. You'll need a connected runner configured with your repository, credentials, and tools. ```python import asyncio from omnigent_client import OmnigentClient async def main(): async with OmnigentClient( base_url="http://127.0.0.1:6767", # For a server requiring a bearer token: # headers={"Authorization": "Bearer "}, ) as client: agent = await client.sessions.resolve_agent("claude-native-ui") runner_id = await client.sessions.resolve_online_runner(harness=agent.harness) if runner_id is None: raise RuntimeError("Connect a runner that supports native Claude Code first") session = await client.sessions.create_from_agent_id(agent.id, title="Code review") await client.sessions.bind_runner(session.id, runner_id=runner_id) print(session.id) print(f"http://127.0.0.1:6767/c/{session.id}") asyncio.run(main()) ``` Use the printed session ID to send a message in the next step, or open the URL to view the session. **REST API** Create a native Claude Code session with `curl` and `jq`. Connect the machine containing your repository and keep this command running while you use the session: ```bash omni host --server http://127.0.0.1:6767 ``` For shared servers, include your authentication credentials in each request, such as `-H "Authorization: Bearer $ACCESS_TOKEN"`. **Use case:** Find an agent **Example** Get the agent ID for native Claude Code: ```bash curl -fsS http://127.0.0.1:6767/v1/agents \ | jq -r '.data[] | select(.harness == "claude-native") | .id' ``` **Use case:** Find a connected host **Example** List host names and IDs: ```bash curl -fsS http://127.0.0.1:6767/v1/hosts \ | jq '.hosts[] | {name, host_id}' ``` Get the ID of a specific host: ```bash curl -fsS http://127.0.0.1:6767/v1/hosts \ | jq -r '.hosts[] | select(.name == "") | .host_id' ``` **Use case:** Start on a connected host **Example** Use your agent ID, host ID, and repository path. `SESSION_ID` stores the new session's ID: ```bash SESSION_ID=$(curl --fail-with-body http://127.0.0.1:6767/v1/sessions \ -H 'Content-Type: application/json' \ -d '{ "agent_id": "ag_reviewer", "host_id": "host_example", "workspace": "/home/ci/my-repository", "title": "Review latest commit" }' | jq -er '.id') ``` **Use case:** Start in a cloud sandbox **Example** For a configured [cloud sandbox host](https://omnigent.ai/docs/deploy/cloud-sandbox-host), use `host_type: "managed"` instead of `host_id` in the create request above. The server provisions the host asynchronously. **Use case:** Check whether the session is ready **Example** Check that `runner_online` is `true` before sending a message: ```bash curl --fail-with-body "http://127.0.0.1:6767/v1/sessions/$SESSION_ID" | jq ``` ## Send a message or continue a conversation Use the session ID from the previous step to send a message to that session. Saved sessions can also be opened in the [Web UI](https://omnigent.ai/docs/interact/web-ui). **CLI** Use `--continue` (or `-c`) to send a follow-up prompt to the most recent conversation for the same agent: ```bash omni run ./my-agent/ --continue -p "Now check whether the tests cover those changes" ``` To continue a specific conversation, use its ID: ```bash omni run ./my-agent/ --resume "" -p "Explain the first finding" ``` Use `--resume` when your automation runs multiple conversations and needs to target a specific one. Use the ID from the CLI's session URL, or look it up with `GET /v1/sessions`. **Python** This example sends a message to an existing session. Replace `conv_abc123` with its ID. The session must have an online runner with access to the repository, model credentials, and tools. The SDK does not start a runner for you. ```python import asyncio from omnigent_client import OmnigentClient async def main(): async with OmnigentClient(base_url="http://127.0.0.1:6767") as client: await client.sessions.post_event("conv_abc123", { "type": "message", "data": { "role": "user", "content": [{"type": "input_text", "text": "tell me a joke"}], }, }) asyncio.run(main()) ``` The call returns when the server accepts the message; it does not wait for the agent's reply. Open the session in the Web UI to follow its progress. **REST API** Send the first prompt or a follow-up to the same session: ```bash curl --fail-with-body "http://127.0.0.1:6767/v1/sessions/$SESSION_ID/events" \ -H 'Content-Type: application/json' \ -d '{ "type": "message", "data": { "role": "user", "content": [{"type": "input_text", "text": "Review the changes in the latest commit"}] } }' ``` The request returns when the server accepts the message, without waiting for the reply. Print the URL to follow the review: ```bash echo "http://127.0.0.1:6767/c/$SESSION_ID" ``` ## Find sessions and follow their progress **CLI** The CLI prints the session URL to stderr at startup and the collected assistant text to stdout when the run finishes. Open that URL to see the conversation and its progress. Use the Python or REST tab to list saved sessions, retrieve history, or monitor an agent from your application. **Python** List recent sessions, then read a session's status and messages. Replace `conv_abc123` with the ID you want to inspect. ```python import asyncio from omnigent_client import OmnigentClient async def main(): async with OmnigentClient(base_url="http://127.0.0.1:6767") as client: sessions = await client.sessions.list(limit=20, sort_by="updated_at") for session in sessions: print(session.id, session.title, session.status) session_id = "conv_abc123" session = await client.sessions.get(session_id) print(session.status) print(await client.sessions.list_items(session_id)) asyncio.run(main()) ``` For live events, iterate over `client.sessions.stream(session_id)`. Use `session.status` to check the session itself and `await client.sessions.subtree_busy(session_id)` to check whether any of its descendants are busy. History is paginated. Use the last item's `id` as `after` in the next `list_items` call to read further. Live events do not replay history; retrieve the snapshot and history again after reconnecting. **REST API** List sessions on a local single-user server: ```bash curl --fail-with-body "http://127.0.0.1:6767/v1/sessions?limit=20&sort_by=updated_at" ``` Filter by `agent_name`, `agent_id`, or `search_query`. Listings exclude archived sessions and child sessions by default; use `include_archived=true` or `kind=any` when you need them. Both session and history listings are paginated: when `has_more` is true, use the returned `last_id` as the next request's `after` cursor. ```bash curl --fail-with-body "http://127.0.0.1:6767/v1/sessions//items" ``` The SSE stream supplies live updates; it does not replay conversation history. Use the session snapshot and `/items` to recover state after reconnecting. ## Fork, archive, or stop work **CLI** To fork into an interactive session: ```bash omni run ./my-agent/ --fork "" ``` `--fork` cannot be combined with `-p`. For scripted forks, archiving, or cancellation, use the Python or REST tab. **Python** These calls run inside the same `async with OmnigentClient(...) as client` block used above. Replace `session_id` with the session you want to manage. | Action | SDK call | | --------------------------- | ---------------------------------------------------------------- | | Cancel active work | `await client.sessions.interrupt(session_id)` | | Fork a conversation | `fork = await client.sessions.fork(session_id)` | | Archive and retain history | `await client.sessions.set_archived(session_id, archived=True)` | | Restore an archived session | `await client.sessions.set_archived(session_id, archived=False)` | A fork's new ID is `fork["id"]`. Bind a runner before sending it a prompt: ```python await client.sessions.bind_runner(fork["id"], runner_id=runner_id) ``` Forking copies the full history by default. Pass `up_to_response_id` to fork from an earlier response. Use the REST tab to permanently delete a session and its resources. **REST API** Use the `SESSION_ID` from the earlier examples. **Use case:** Cancel active work **Example** Stop the current task without deleting the conversation: ```bash curl --fail-with-body "http://127.0.0.1:6767/v1/sessions/$SESSION_ID/events" \ -H 'Content-Type: application/json' \ -d '{"type": "interrupt"}' ``` **Use case:** Fork a session **Example** Copy the conversation and save the new session's ID in `FORK_ID`: ```bash FORK_ID=$(curl --fail-with-body "http://127.0.0.1:6767/v1/sessions/$SESSION_ID/fork" \ -H 'Content-Type: application/json' \ -d '{"title": "Explore another approach"}' | jq -er '.id') ``` This copies the full history. Add `"up_to_response_id": ""` to the body to copy history only through that response. Bind a runner to the fork before sending another prompt, or request a managed host in the fork body. To continue the conversation on a different agent, add `"agent_id"` (a built-in agent listed by `GET /v1/agents`) to the fork body. Forking is the only way to change agents: a session's bound agent can't be switched in place, and `POST /v1/sessions/{id}/switch-agent` returns `410 Gone`. Add `"side_chat": true` (default `false`) to mark the fork as a [side chat](https://omnigent.ai/docs/interact/web-ui#side-chats): the fork is labeled `omnigent.side_chat` and omitted from `GET /v1/sessions`, so it stays out of the web UI's sidebar and appears only as a Workspace-rail tab. It otherwise behaves like any other fork, with its own runner and transcript. **Use case:** Archive a session **Example** Keep its history while hiding it from default listings: ```bash curl --fail-with-body -X PATCH "http://127.0.0.1:6767/v1/sessions/$SESSION_ID" \ -H 'Content-Type: application/json' \ -d '{"archived": true}' | jq ``` **Use case:** Restore a session **Example** Make an archived session appear in default listings again: ```bash curl --fail-with-body -X PATCH "http://127.0.0.1:6767/v1/sessions/$SESSION_ID" \ -H 'Content-Type: application/json' \ -d '{"archived": false}' | jq ``` **Use case:** Delete a session **Example** Permanently delete the session and its associated resources. Requires owner-level access: ```bash curl --fail-with-body -X DELETE "http://127.0.0.1:6767/v1/sessions/$SESSION_ID" | jq ``` ## Share a session Use the permissions API to share a session on a reachable shared server. Your credentials need **manage or owner access** to the session, and the server's [sharing settings](https://omnigent.ai/docs/collaborate/auth#session-sharing) must allow the requested access. The examples use bearer-token authentication: ```bash SERVER_URL="" SESSION_ID="" ACCESS_TOKEN="" ``` For a specific user, `level` is **1** for read access, **2** to also send messages and interact with the session, or **3** to also manage sharing. Public access is always read-only (`level: 1`). **Use case:** Share with a specific user **Example** Use the recipient's user ID on this server, such as their account username or SSO email. Repeating this request updates their existing access level. ```bash curl --fail-with-body -X PUT "$SERVER_URL/v1/sessions/$SESSION_ID/permissions" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"user_id": "", "level": 1}' ``` **Use case:** Make the session public **Example** Grant `__public__` read access so anyone who can sign in to the server can view the session through its URL, without an individual grant. The server must have public sharing enabled; this does not bypass sign-in. ```bash curl --fail-with-body -X PUT "$SERVER_URL/v1/sessions/$SESSION_ID/permissions" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"user_id": "__public__", "level": 1}' ``` **Use case:** Inspect current grants **Example** ```bash curl --fail-with-body "$SERVER_URL/v1/sessions/$SESSION_ID/permissions" \ -H "Authorization: Bearer $ACCESS_TOKEN" | jq ``` **Use case:** Remove public access **Example** Revoke the public grant; specific users' grants remain. To revoke a specific user instead, use their URL-encoded user ID in place of `__public__`. ```bash curl --fail-with-body -X DELETE \ "$SERVER_URL/v1/sessions/$SESSION_ID/permissions/__public__" \ -H "Authorization: Bearer $ACCESS_TOKEN" ``` Send the recipient the session URL. A specific user must sign in with the identity you granted access to: ```bash echo "$SERVER_URL/c/$SESSION_ID" ``` You can also let an agent share sessions through `sys_session_share`. Set `agent_session_sharing: non-public` in its agent configuration to allow sharing with users, or `agent_session_sharing: public` to also allow public grants. The tool accepts `user_id`, `level` (`"read"`, `"edit"`, or `"manage"`), and an optional `session_id` that defaults to the calling session. The caller's permissions and server sharing settings still apply. ## Edit an installed agent's configuration Agents you install on a server keep their `config.yaml` bundle server-side. Instead of re-uploading the whole bundle to change a description, prompt, model, harness, or MCP server, you can read the editable fields and send a partial update over the REST API. Only the agent's owner can read or edit its configuration, even when a session that uses the agent is shared. Check the server advertises support first: `GET /v1/info` returns `agent_config_edit: true` when configuration editing is available. Older servers omit the field; treat that as unsupported. On servers that lack support, the endpoints below return `501`. ```bash SERVER_URL="http://127.0.0.1:6767" AGENT_ID="" # from GET /v1/agents ``` **Use case:** Read the editable configuration **Example** Returns `id`, `name`, `version`, `description`, `harness`, `model`, the literal `instructions`, and `mcp_servers` keyed by server name. Each MCP entry lists its `transport`, `description`, `url`, `command`, `args`, and `editable` flag, plus the names of configured credentials in `configured_header_names` and `configured_env_names`. Credential values are never returned, and responses are sent with `Cache-Control: no-store`. ```bash curl -fsS -i "$SERVER_URL/v1/agents/$AGENT_ID/config" ``` Save the `ETag` response header; the update request requires it: ```bash ETAG=$(curl -fsS -D - -o /dev/null "$SERVER_URL/v1/agents/$AGENT_ID/config" \ | tr -d '\r' | awk 'tolower($1) == "etag:" { print $2 }') ``` **Use case:** Update only the fields you send **Example** Send a JSON body with `If-Match` set to the saved ETag. Omitted fields keep their current contents, including credentials, skills, and other bundled files. The response returns the updated configuration and a new `ETag`. ```bash curl --fail-with-body -X PATCH "$SERVER_URL/v1/agents/$AGENT_ID" \ -H 'Content-Type: application/json' \ -H "If-Match: $ETAG" \ -d '{ "description": "Reviews migrations", "instructions": "Check compatibility and rollback steps.", "mcp_servers": { "docs": {"url": "https://docs.example.com/mcp"}, "old-tool": null } }' | jq ``` A request without `If-Match` returns `428`; a stale tag returns `412` (`precondition_failed`). Reload the configuration and reapply your change before retrying. The body accepts these top-level fields. Unknown fields are rejected, and the agent's `name` and `executor.type` cannot change. | Field | Behavior | | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `description` | Replaces the description; `null` clears it. | | `instructions` | Replaces the literal instructions; `null` or `""` clears them. Clearing does not restore a bundled `AGENTS.md`. | | `model` | Nonempty string that replaces `executor.model`. Requires `executor.type: omnigent`; other executors return `409`. | | `harness` | Nonempty harness id that replaces `executor.config.harness`. Requires `executor.type: omnigent`. See [Harnesses](https://omnigent.ai/docs/build/harnesses). | | `mcp_servers` | Map of server name to a partial declaration or `null`. `null` deletes the server, an object patches it, and `{}` changes nothing. New servers need a complete `http` or `stdio` declaration including `transport`. | Within an MCP server patch: - `transport` cannot change on an existing server. Managed registry servers (`editable: false` in the read response) are read-only and return `409`. - `headers` and `env` merge by key: a `null` value deletes that key, a `null` map clears every key, and `{}` preserves the map. Omit credentials you are not changing; `"[REDACTED]"` placeholders are rejected. - `args` replaces the whole array; `[]` clears it. - New servers are written as sidecar files under [`tools/mcp/.yaml`](https://omnigent.ai/docs/build/tools#sidecar-mcp-files); existing declarations are edited in place. The same safety checks as the session MCP routes apply. A few things to know: - Only standard `config.yaml` bundles are editable. Other formats return `409` with the code `agent_config_not_editable`. - Edited YAML loses its comments and formatting; untouched files keep their contents. The agent keeps its ID and gets a new `version`; a patch that changes nothing leaves the version unchanged. - Sessions pick up the new configuration on their next turn. Active turns are not interrupted, and per-session overrides still take precedence. - Re-uploading the bundle through the install API still replaces the whole bundle; use it when you need to change files the PATCH fields don't cover. ## API endpoints reference The server exposes these agent configuration endpoints. Replace `{id}` with an agent ID from `GET /v1/agents`. | Method | Path | What you can do | | ------- | ------------------------ | ------------------------------------------------------------------ | | `GET` | `/v1/agents/{id}/config` | Read an owned agent's editable configuration and its `ETag` | | `PATCH` | `/v1/agents/{id}` | Update only the supplied configuration fields; requires `If-Match` | The server exposes these session endpoints. Replace `{id}` with a session ID. | Method | Path | What you can do | | -------- | ----------------------------------------- | --------------------------------------------- | | `POST` | `/v1/sessions` | Create a session | | `GET` | `/v1/sessions` | List and filter sessions | | `GET` | `/v1/sessions/{id}` | Read session details and status | | `PATCH` | `/v1/sessions/{id}` | Rename, configure, archive, or unarchive | | `DELETE` | `/v1/sessions/{id}` | Delete a session and its associated resources | | `POST` | `/v1/sessions/{id}/fork` | Copy conversation history into a new session | | `GET` | `/v1/sessions/{id}/items` | Read conversation history | | `GET` | `/v1/sessions/{id}/child_sessions` | List child sessions | | `GET` | `/v1/sessions/{id}/stream` | Receive live events via SSE | | `POST` | `/v1/sessions/{id}/events` | Send a message or interrupt active work | | `PUT` | `/v1/sessions/{id}/permissions` | Grant or update user/public access | | `GET` | `/v1/sessions/{id}/permissions` | List permission grants | | `DELETE` | `/v1/sessions/{id}/permissions/{user_id}` | Revoke a permission grant | See the [API Reference](https://omnigent.ai/reference) for more details on these and other endpoints. ## Automation features ### Let an agent manage other sessions You can ask an agent to delegate work to another Omnigent agent. It uses built-in session tools to create a child conversation, send instructions, and read the results. These are tools the agent calls during a session; you don't need to write an API client to use them. | Tool | What it does | | ------------------------- | ---------------------------------------------------------------------------------------------------------- | | `sys_agent_list` | Find available agents and their IDs. | | `sys_session_create` | Create a child session from an existing agent ID or a local agent config, optionally with a first message. | | `sys_session_send` | Send a follow-up to a child session, or start a named sub-agent declared by the parent agent. | | `sys_session_list` | Find child sessions and other sessions you can access. | | `sys_session_get_info` | Check a session's status, agent, and workspace. | | `sys_session_get_history` | Read a session's recent messages and tool results. | | `sys_session_close` | Close a finished child session while retaining its history. | For example, send this prompt in an Omnigent session: ```text Find the native Claude Code agent and create a child session titled "Review tests". Ask it to review the test coverage in this repository without editing files. Read its findings and summarize the missing coverage for me. ``` The agent can use `sys_agent_list` to find the agent, then call `sys_session_create` with arguments like these: ```json { "agent_id": "", "title": "Review tests", "message": "Review the test coverage in this repository. Report missing coverage without editing files." } ``` The returned `conversation_id` identifies the child. To continue its work, ask: ```text Ask the "Review tests" child session to prioritize its findings by risk. ``` `sys_session_send` can write only to the calling session's direct children. For agents with named sub-agents, reusing the same `agent` and `title` continues that child conversation; a different title starts a separate one. Child sessions appear in the Web UI, where you can follow their progress. ### Use bundled skills for multi-agent workflows Skills are reusable instructions that guide how an agent uses its tools. The skills available depend on the agent you run. For example, [Polly](https://omnigent.ai/docs/use/builtin-agents/polly) bundles these coding workflows: | Skill | When to use it | | -------------- | --------------------------------------------------------------------------------------------------- | | `investigate` | Delegate code exploration or debugging, then combine the findings. | | `fanout` | Split independent implementation tasks across workers, each in its own worktree and PR. | | `cross-review` | Have a different vendor's agent review an implementation and send blocking findings back for fixes. | Start Polly in your repository: ```bash omni polly ``` Then name the skill in your prompt: ```text Use the investigate skill to trace how this repository authenticates API requests. Have separate agents examine the server routes and client code, then summarize the flow with file references. Do not edit files. ``` For a change that is ready for review: ```text Use cross-review to review PR #123 against its stated requirements. ``` Polly needs the worker harnesses and their credentials configured. The skills coordinate the work; the workers still need access to the repository and models. See [Prompts & Skills](https://omnigent.ai/docs/build/prompts) to add your own workflows in an agent's `skills/` directory or the project's `.agents/skills/` directory. ### Schedule recurring agent runs Ask an agent to create a scheduled task when you want the same work repeated. Omnigent provides four built-in tools: | Tool | What it does | | --------------------------- | ---------------------------------------------------- | | `sys_scheduled_task_create` | Save a prompt, agent, and recurring schedule. | | `sys_scheduled_task_list` | List your scheduled tasks. | | `sys_scheduled_task_update` | Change a task's prompt, schedule, or other settings. | | `sys_scheduled_task_delete` | Remove a task so it no longer runs. | For example, replace the host name and repository path in this prompt: ```text Create a scheduled task named "Weekday code review". Every weekday at 9am Asia/Singapore, run native Claude Code on host "my-host" in /home/ci/my-repository. Ask it to review the latest commit and report findings without editing files. Look up the agent and host IDs first. ``` The schedule uses this recurrence rule with `timezone: "Asia/Singapore"`: ```text FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR;BYHOUR=9;BYMINUTE=0 ``` The chosen host must be online when the task fires, and the agent needs credentials and tool permissions suitable for unattended work. You can later ask, "Move Weekday code review to 10am," or "Delete Weekday code review." The agent uses the list and update or delete tools to make the change. Creating or updating a task from an agent always pauses for your approval before anything is saved, even when no policies are configured — review the agent, prompt, host, and permission settings in the prompt before accepting. Listing and deleting tasks do not ask. See [Approval for create and update](https://omnigent.ai/docs/build/scheduled-tasks#approval-for-create-and-update). See [Scheduled Tasks](https://omnigent.ai/docs/build/scheduled-tasks) for the full configuration. Scripts can also manage tasks through `/v1/scheduled-tasks`; machine client-credentials tokens do not grant access to that endpoint. --- ## https://omnigent.ai/docs/reference/configuration # Configuration reference Omnigent configuration controls the CLI, local server, harnesses, and managed sandboxes. These settings are separate from an agent's portable YAML spec and from the [REST API](https://omnigent.ai/reference). ## Configuration scopes | Scope | File or command | Use | | --------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | | Project | `.omnigent/config.yaml` or `omni config set KEY=VALUE` | Defaults for the current project. Project values override user values, except [user-level-only settings](#user-level-only-settings). | | User | `~/.omnigent/config.yaml` or `omni config set --global KEY=VALUE` | Defaults shared across projects and settings owned by the local server. | | Deployed server | `omni server --config /path/to/config.yaml` | Server-wide settings for every user and session on that deployment. | Run `omni config list` to inspect the effective user and project configuration. Use `omni config unset KEY` or `omni config unset --global KEY` to remove a value from the corresponding scope. Not every key is valid in every scope. Each setting below identifies its scope, type, default, and validation behavior. ### User-level-only settings Some settings are read only from `~/.omnigent/config.yaml`. A project's `.omnigent/config.yaml` can't change them: - **`server`**: which server the CLI connects to. Setting it without `--global` is rejected, and a `server` value in a project file is ignored. To use a different server for one command, pass `--server`. - **`session_title_instructions`**: see [Automatic session titles](#automatic-session-titles). - **Per-harness `command` and `args`** under the `harness:` key. A project can still select the default harness (`harness: ` or `harness.default`). See [Override the harness binary and launch args](https://omnigent.ai/docs/build/harnesses/configuration#override-the-harness-binary-and-launch-args). `omni config list` flags project-file values it ignores: user-level-only keys appear as `# ignored user-level-only setting(s): ... (set with --global)`, and per-harness launch entries appear as `# ignored project harness launch overrides: ...`. ## Automatic session titles | Setting | Value | | ------- | ---------------------------------------------------- | | Key | `session_title_instructions` | | Scope | User or deployed server; project values are rejected | | Type | String, up to 4,000 characters | | Default | No additional instructions | Use natural-language instructions to control the format of session titles. These requirements apply both to titles generated from the first message in a session and to later agent-initiated renames through the `sys_session_rename` tool: ``` omni config set --global \ 'session_title_instructions=Prefix pull request titles with the PR number and use a short snake_case name.' ``` This setting belongs to the server, not an agent's portable instructions. It must use the global scope for a local server. On a deployed server, set the same key in the YAML passed to `omni server --config`: ```yaml session_title_instructions: | Prefix pull request titles with the PR number. Use a short snake_case name for other work. ``` The generator receives the current date as `YYYY-MM-DD` along with the first message. Default generated titles are limited to 100 characters; output beyond that limit is rejected in favor of the first-message fallback. When custom instructions are set, generated titles may use up to 200 characters and longer output is truncated with an ellipsis. Manually assigned titles also have a 200-character limit. When an agent renames a session with `sys_session_rename`, its proposed title is formatted against these same requirements before the rename is applied. If that formatting fails, the existing title is preserved rather than replaced by an unformatted proposal. Without custom instructions, the agent's proposal is used as-is. Manually assigned titles are never reformatted. The setting applies after the local server restarts, to both new-session titles and agent renames. An empty value removes the additional guidance. A non-string value or a string longer than 4,000 characters is ignored and the default title prompt is used. ## Web UI base path | Setting | Value | | ------- | ------------------------------------------------------------------------------------ | | Key | `OMNIGENT_WEB_BASE_PATH` environment variable, or `omni server --base-path ` | | Scope | Server process only (environment or CLI flag); not a `config.yaml` key | | Type | URL path prefix — leading slash, no trailing slash, e.g. `/proxy/6767` | | Default | Unset — served at the origin root (`/`) | Serves the server and web UI under a path prefix behind a reverse proxy such as code-server's `/proxy//` or an nginx `location` block. The web UI prefixes its API, SSE, WebSocket, and asset URLs, and the server accepts requests with or without the prefix, so prefix-stripping and non-rewriting proxies both work from one value. The flag is shorthand for the environment variable and wins over an inherited value. `""` and `/` are treated as unset. A missing leading slash is added and a trailing slash is removed. Values that start with `//`, contain characters outside letters, digits, and `-._~/`, include `.`/`..` segments, or whose first segment collides with a reserved API namespace or a top-level web UI route are rejected at startup. For the full list of reserved segments, the auth variables to pair with it, and how the local managed server remembers the prefix, see [Serving under a subpath](https://omnigent.ai/docs/deploy/overview#serving-under-a-subpath). ## Attachment limits | Setting | Value | | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | Keys | `filesystem_attachment_max_bytes`, `filesystem_attachment_max_files`, `filesystem_attachment_max_total_bytes`, `filesystem_attachment_denied_extensions` | | Scope | Server config (`omni server --config`) | | Applies | `.zip`, `.docx`, `.xlsx`, `.pptx`, `.db`, `.sqlite`, `.sqlite3` uploads | Uploads of ZIP archives, Office documents, and SQLite databases are admitted by filename extension and stored without extraction; the harness reads a local copy outside the working checkout (see [Attach archives, Office documents, and databases](https://omnigent.ai/docs/interact/web-ui#attach-archives-office-documents-and-databases)). These formats require Claude Code (`claude-native`) or Codex (`codex-native`) and an execution host/runner that supports their delivery and restoration. The upload, copy, and fork routes apply the following limits to them: | Key | Default | Scope | | ----------------------------------------- | --------------------- | ------------------------------------------------------ | | `filesystem_attachment_max_bytes` | `52428800` (50 MiB) | Bytes per file | | `filesystem_attachment_max_files` | `20` | Stored files of these types per session | | `filesystem_attachment_max_total_bytes` | `209715200` (200 MiB) | Combined bytes of these types per session | | `filesystem_attachment_denied_extensions` | `[]` | Further restrict the allowlist, e.g. `[".zip", ".db"]` | ```yaml filesystem_attachment_max_bytes: 26214400 # 25 MiB per file filesystem_attachment_max_files: 10 filesystem_attachment_max_total_bytes: 104857600 # 100 MiB per session filesystem_attachment_denied_extensions: [".zip", "db"] # leading dot optional ``` Denied-extension entries are matched case-insensitively, with or without the leading dot. Entries that are not non-empty strings are skipped with a warning; if the value is not a list, the whole setting is ignored. The denylist can only narrow the built-in allowlist, and it does not restrict files the agent creates or downloads through other tools. These are filename-based admission and storage limits, not content inspection: the server does not identify file formats from their bytes, inspect archive entries, or detect renamed binary content. Images, PDFs, and text/code uploads keep their separate existing limits. The web composer also has a fixed 50 MiB ceiling for these formats, so raising `filesystem_attachment_max_bytes` above that has no effect for uploads made from the web UI. **Sandbox access.** The runner writes attachment copies to `~/.omnigent/attachments//` (or `$OMNIGENT_DATA_DIR/attachments/` when overridden) on the execution host, and the harness receives that absolute path. Custom sandbox and provider configurations must let the runner write this cache and the harness's file-reading tools read it; permission to read the working directory does not cover `attachments/`. If the harness runs in a separate filesystem namespace, expose the session cache at the same absolute path. Scope any added access to the required cache rather than the whole data directory, which also contains credentials. Request-phase [custom policies](https://omnigent.ai/docs/policies/custom#reference-policyevent) receive the filename and content type of these attachments with an empty `text` value; their contents are not scanned as text. ## Managed sandboxes - [Kubernetes sandbox configuration](https://omnigent.ai/docs/reference/configuration/kubernetes) - [OS sandbox configuration](https://omnigent.ai/docs/reference/configuration/os-sandbox) - [Cloud sandbox configuration](https://omnigent.ai/docs/reference/configuration/cloud-sandbox) ## Routing - [Smart Routing configuration](https://omnigent.ai/docs/reference/configuration/routing) --- ## https://omnigent.ai/docs/reference/configuration/cloud-sandbox # Cloud sandbox configuration The server can provision remote runner hosts through several sandbox providers. See [Cloud Sandbox Host](https://omnigent.ai/docs/deploy/cloud-sandbox-host) for installation, provider authentication, and launch steps. To add a provider from a separate Python package, see [Community Sandbox Providers](https://omnigent.ai/docs/deploy/community-sandbox-providers). ## Common settings Add the top-level `sandbox` block to the server configuration: ```yaml sandbox: provider: modal server_url: https://your-server.example.com ``` | Field | Required | Description | | ------------- | -------- | --------------------------------------------------------------------------------------------------------------------- | | `provider` | Yes | Provider id: `databricks`, `modal`, `daytona`, `islo`, `blaxel`, `gensee`, `openshell`, `boxlite`, or `microsandbox`. | | `server_url` | Yes | Omnigent server URL reachable from the sandbox. | | `host_config` | No | Omnigent configuration installed inside each sandbox. See [Host configuration](#host-configuration). | | `git_clone` | No | Clone policy for fresh repository checkouts. See [Git clone options](#git-clone-options). | Provider-specific blocks are optional. Omit one to use that provider's defaults. ## Git clone options The optional `git_clone` mapping reduces the Git history a managed sandbox downloads when it creates a repository checkout. A useful starting point for short coding sessions: ```yaml sandbox: provider: kubernetes server_url: https://your-server.example.com git_clone: depth: 50 single_branch: true ``` This checks out every file on the selected branch with up to 50 commits of history. It applies to each requested repository, including repositories cloned after claiming a warm-pool Pod, and needs no prepared workspace image. Restart the server after changing the block, then create a new sandbox to try it. | Setting | Default | Behavior | | --------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `depth` | `null` | Full history. A positive integer requests a shallow clone with that many commits. | | `single_branch` | `null` | Keep the existing rule: a pinned repo branch selects only that branch; otherwise all branches are fetched. `true` selects only the requested (or default) branch; `false` fetches all branches. | | `filter` | `null` | `blob:none` enables a partial clone: commit history is kept and file contents are fetched on demand. The initial checkout still includes every file. Requires remote filter support; Git can warn and fall back to an unfiltered clone. | | `tags` | `true` | Normal tag following. `false` passes `--no-tags`, which persists for later fetches. `true` does not promise every remote tag is fetched. | Only these four keys are accepted; any other key, or any non-null, non-mapping value, fails server startup. Omitting `git_clone` preserves the existing clone commands. Setting only `depth` keeps the existing branch selection — Omnigent explicitly counters Git's implicit single-branch default for shallow clones. Fetching every branch of a repository with many branch tips can erase most of the speed benefit; set `single_branch: true` when that tradeoff fits the workload. The policy applies only when a checkout has to be created. Retained checkouts are not reset, truncated, or reconfigured when a session wakes; an agent-sandbox wake that lost its ephemeral workspace recreates it with the current policy. Each entry in a `sandbox.providers` list can override the entire shared `git_clone` mapping; use `git_clone: {}` on an entry to restore the defaults. Kubernetes (including agent-sandbox warm pools) and providers that use the standard exec clone implementation support these options. Gensee, Islo, and custom workspace materializers must implement support explicitly; with nondefault settings the launch, replacement, or resume is rejected before any sandbox is provisioned, naming the provider. Third-party providers advertise support with `SandboxCapabilities.git_clone_options=True` and honor `RepoWorkspace.git_clone`; exec providers that override `materialize_workspace` must accept its optional `git_clone` argument when opting in. With default settings, legacy overrides receive no new keyword. Clone settings carry no credentials. Initial clones keep using the owner-bound [GitHub broker](https://omnigent.ai/docs/deploy/github-connect), and later Git operations use the host's existing credential helper. Deepening a private repo or lazily fetching blobs requires continued access to the remote. ### Getting more history later Inside a shallow checkout: ```bash git fetch --deepen=100 origin # Or restore full history for the configured branch selection: git fetch --unshallow origin ``` Neither command widens a single-branch fetch refspec or re-enables tags. To fetch another branch, for example for a PR comparison, name its remote-tracking destination: ```bash git fetch origin '+refs/heads/main:refs/remotes/origin/main' # If both branches need more history, include both refspecs: git fetch --deepen=100 origin \ '+refs/heads/main:refs/remotes/origin/main' \ '+refs/heads/feature:refs/remotes/origin/feature' git fetch --tags origin # explicitly fetch tags if needed ``` When a local PR diff cannot find a merge base because history is shallow, or the base branch was never fetched, Omnigent reports an actionable error with these commands rather than showing a misleading branch-tip comparison. A failed lazy file fetch is also an error; unavailable content is never treated as an added or deleted file. Keep the full-history defaults for history-heavy development, offline work, or tools that expect broad history. `blob:none` is opt-in because it moves work to later reads: a first historical `git show` or `git blame` can fetch more data and take longer. Sparse checkout, submodule recursion, and LFS behavior are unchanged. ## Databricks [Databricks Sandbox](https://docs.databricks.com/aws/en/compute/serverless/sandbox) is a **managed-host provider only**. The server drives sandboxes through the [Databricks SDK](https://databricks-sdk-py.readthedocs.io/en/latest/workspace/sandbox/sandbox.html). To use Databricks Sandboxes, the Sandbox preview setting must be enabled for the workspace. ```yaml sandbox: provider: databricks server_url: https://your-server.example.com # public URL the sandbox host dials back to databricks: workspace_host: https://.cloud.databricks.com # required: the workspace Sandboxes run in inactivity_timeout_s: 3600 # optional idle stop sandbox_id_prefix: omnigent # optional bootstrap_command: uv tool install omnigent # optional, run before each host start ``` | Setting | Default | Purpose | | ---------------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `workspace_host` | required | The workspace Sandboxes run in. A user whose connected Databricks account targets any other workspace is refused, so their Sandboxes are never orphaned. | | `proxy_bearer` | `false` | Write a short-lived bearer (the owner's own token) into the sandbox so its host can reach the server through an authenticating reverse proxy, such as the Databricks Apps OAuth proxy. | | `inactivity_timeout_s` | none | Idle stop; the sandbox is resumed in place on the session's next turn. Optional; if set, must be at least 1200 seconds (twice the 600-second liveness keepalive). | | `sandbox_id_prefix` | `omnigent` | Prefix for generated sandbox ids. | | `bootstrap_command` | none | Shell command run before each host start and resume, for example a `uv tool install` that pins omnigent over the image's baked version. | **Identity.** Every Sandbox is created and driven as the user who started the session, using the Databricks account they connected under **Settings → Connections → Databricks**. The credential inside the Sandbox — and with `proxy_bearer: true` its bearer file — is that user's own. This needs a per-user Databricks account connection configured on the server: an account-level custom OAuth app integration with the `all-apis offline_access` scopes. ## Modal ```yaml sandbox: provider: modal server_url: https://your-server.example.com modal: image: ghcr.io/omnigent-ai/omnigent-host:latest secrets: [omnigent-llm] ``` - `image` defaults to the official Omnigent host image. - `secrets` names [Modal secrets](https://modal.com/docs/guide/secrets) to inject into the runner. Values remain in Modal's secret store. ## Daytona ```yaml sandbox: provider: daytona server_url: https://your-server.example.com daytona: image: docker.io/you/omnigent-host:latest env: [OPENAI_API_KEY, ANTHROPIC_API_KEY] ``` - `image` defaults to the official Omnigent host image. - `env` lists server environment variables copied into each sandbox. A listed variable that is not set fails the launch. Daytona has no platform lifetime cap. Omnigent disables Daytona's 15-minute idle auto-stop when it provisions a sandbox, so a host survives gaps between turns, and deletes the sandbox when its session is deleted. If a managed sandbox is stopped or archived out-of-band, the next message resumes the same sandbox in place, preserving its filesystem and workspace; an archived sandbox may first need a restore, so the wake waits up to 15 minutes (the same budget as a cold create). A sandbox that is mid-transition (starting, stopping, or archiving) is waited on rather than replaced. Only when the sandbox has been deleted does Omnigent create a replacement with a fresh workspace; a sandbox that is still being deleted, or a provider error during the wake, fails the turn with a retryable error and leaves the existing sandbox binding intact. Daytona Tier 1 and 2 organizations restrict outbound traffic to a [fixed allowlist](https://www.daytona.io/docs/en/network-limits). The `server_url` and model endpoints must be reachable from Daytona. Tier 3 and higher lift that restriction. On the free tier, use a Cloudflare Worker relay on the allowlisted `*.workers.dev` domain; the repository includes an example under `deploy/daytona/`. Modal has full egress on its entry tier. ## Islo ```yaml sandbox: provider: islo server_url: https://your-server.example.com islo: image: ghcr.io/omnigent-ai/omnigent-host:latest env: [OPENAI_API_KEY, ANTHROPIC_API_KEY] idle_pause_after_s: 900 ``` - `image` defaults to the official Omnigent host image. - `env` lists server environment variables copied into each sandbox. A listed variable that is not set fails the launch. - `idle_pause_after_s` defaults to `900`. Set it to `null` to disable idle pause. When a new message arrives for a paused Islo host, Omnigent resumes the same sandbox and restarts the runner. Changing `idle_pause_after_s` affects new sandboxes, not existing ones. Deleting the session still deletes the sandbox. The provider uses `ISLO_API_KEY`. `ISLO_BASE_URL` and `ISLO_COMPUTE_URL` can override its service endpoints. ## Blaxel ```yaml sandbox: provider: blaxel server_url: https://your-server.example.com blaxel: image: blaxel/omnigent-host:latest env: [OPENAI_API_KEY, ANTHROPIC_API_KEY] region: us-was-1 memory_mb: 4096 ttl: 24h ``` - `image` defaults to `blaxel/omnigent-host:latest`. - `env` lists server environment variables copied into each sandbox. - `region` defaults to `BL_REGION` or the Blaxel default. - `memory_mb` defaults to `4096`. - `ttl` defaults to `24h` and accepts `w`, `d`, `h`, `m`, and `s`, alone or combined, such as `1h30m`. `ttl` is the sandbox's maximum age from creation, not an idle timeout. Blaxel deletes the sandbox at that age even while its session is active. The launch token lasts for `ttl + 1h`; provider command output is capped at 4 MiB. The default image combines the Omnigent host runtime with Blaxel's required `sandbox-api`. Pin a published tag with `image` when deployments must remain stable across releases. Managed sandboxes use the server process's Blaxel workspace and credentials. Omnigent users are not mapped to individual Blaxel workspaces. Set `OMNIGENT_BLAXEL_HOST_IMAGE` to override the default image without changing the configuration. The Blaxel SDK's telemetry is disabled; keep `DO_NOT_TRACK=1` set on the server to preserve that behavior. ## OpenShell ```yaml sandbox: provider: openshell server_url: https://your-server.example.com openshell: image: ghcr.io/omnigent-ai/omnigent-host:latest env: [OPENAI_API_KEY, ANTHROPIC_API_KEY] cluster: my-gateway workspace: team-alpha ``` - `image` defaults to the official Omnigent host image. - `env` lists server environment variables copied into each sandbox. - `cluster` defaults to the active OpenShell gateway from `$OPENSHELL_GATEWAY` or `~/.config/openshell/active_gateway`. - `workspace` defaults to `$OMNIGENT_OPENSHELL_WORKSPACE`, then `default`. When a server-managed OpenShell sandbox goes dormant, its persistent volume is retained and the session reports a wakeable "asleep" state. The next message resumes the same sandbox in place, preserving its name and volume, and waits for it to become ready before continuing. In-place resume requires `SandboxClient.start`, added in `openshell` 0.0.105. With an older SDK, the host remains offline instead of advertising an unsupported wake action; upgrade with `uv pip install --upgrade 'omnigent[openshell]'`. Resume is currently a cold restart rather than a warm snapshot restore. ## Microsandbox The `microsandbox` provider runs each managed session in a micro-VM on the same machine as the server. Because the guest runs untrusted agent code next to the server, its default `host` network mode exposes only an explicit TCP port allowlist rather than the entire host. ```yaml sandbox: provider: microsandbox server_url: http://host.microsandbox.internal:6767 microsandbox: host_ports: [8317] ``` Use your server's port in `server_url`; `localhost` inside the VM refers to the VM itself. The example also allows a host-local service on port `8317`. Optional `sandbox.microsandbox` settings: - `image` selects a host image; the official Omnigent host image is the default. - `env` lists server environment variable names to copy into the VM, such as `[OPENAI_API_KEY]`. Set the values on the server, not in this YAML. - `cpus` defaults to `2`; `memory_mib` defaults to `4096`. - `idle_timeout_s` defaults to `86400` (24 hours); `0` disables idle draining. Draining releases compute and preserves the writable layer, except `/tmp`. The next message resumes the VM. - `network` is `host` (default), `public-only`, or `all`. `public-only` allows public internet access and requires a public `server_url`; `all` also permits private networks. `host` adds the scoped host access described below. `host.microsandbox.internal` is the stable guest hostname for the machine running microsandbox. In `host` network mode: - The `server_url` port is added to the host allowlist only when `server_url` targets `host.microsandbox.internal`. A public `server_url` does not implicitly open the matching port on the host. - List any additional host-local services the guest needs in `host_ports`, using integer ports from `1` through `65535`. Everything else on the host remains unreachable. ## Boxlite ```yaml sandbox: provider: boxlite server_url: https://your-server.example.com boxlite: image: docker.io/me/omnigent-host:latest env: [OPENAI_API_KEY, GIT_TOKEN] disk_size_gb: 100 cloud: endpoint: https://boxlite.example.com:8100 ``` - `image` defaults to the official Omnigent host image. - `env` lists server environment variables copied into each sandbox. - `disk_size_gb` must be a positive integer. Omit it to use the SDK default. Provide one mode: `cloud.endpoint` selects cloud mode and uses `BOXLITE_API_KEY`; `local.home_dir` and `local.registry` configure local mode. Local mode is used when neither block is set. ## Gensee [Gensee](https://gensee.ai) is a **managed-host provider only**. The server talks to the Gensee control plane over HTTPS; no cloud-provider credentials or Gensee implementation packages are installed in Omnigent. Creating a managed session allocates one sandbox, has its control plane start `omnigent host`, and waits for that host to connect back. Gensee does not implement `omnigent sandbox create`, remote exec/file-copy primitives, stopped-sandbox resume, or workspace fork/merge. The production endpoint and standard token variable are defaults, so the minimal configuration is just `provider` + `server_url`: ```yaml sandbox: provider: gensee server_url: https://your-server.example.com ``` Use an explicit block for a dedicated or development endpoint: ```yaml sandbox: provider: gensee server_url: https://your-server.example.com gensee: endpoint: https://sandbox.gensee.ai api_token_env: GENSEE_CONTROLLER_API_TOKEN workspace_root: /mnt/gensee-tclone/workspaces operation_timeout_s: 900 poll_interval_s: 2 request_timeout_s: 40 retry_timeout_s: 60 env: [] ``` | Setting | Default | Purpose | | --------------------- | ------------------------------- | --------------------------------------------------------------------------------------------- | | `endpoint` | `https://sandbox.gensee.ai` | Gensee HTTPS control-plane base URL. | | `api_token_env` | `GENSEE_CONTROLLER_API_TOKEN` | Server environment variable holding the API token. | | `workspace_root` | `/mnt/gensee-tclone/workspaces` | Absolute workspace root inside each sandbox. | | `operation_timeout_s` | `900` | Shared deadline for host-start submission, retries, and polling; late responses are rejected. | | `poll_interval_s` | `2` | Interval between operation-status requests. | | `request_timeout_s` | `40` | HTTP I/O timeout, capped by the remaining operation budget during host startup. | | `retry_timeout_s` | `60` | Retry budget for transport errors and HTTP 500/502/503/504; `0` disables retries. | | `env` | `[]` | Server environment variable names copied into the sandbox secret. | The endpoint must use HTTPS and cannot contain credentials, a query, or a fragment. Unknown settings and malformed values stop server startup rather than failing on the first user session. `GENSEE_CONTROLLER_URL` overrides the default endpoint when constructing the launcher directly. An explicit `sandbox.gensee.endpoint` takes precedence in the server-managed configuration. The Gensee API token stays in the server and authorizes lifecycle requests. Each sandbox receives a separate server-minted Omnigent host token (valid for seven days) through Gensee's short-lived secret store; it is not included in the durable operation request. By default no model or Git credentials are injected — list only variables every sandbox on the server may receive in `env`, or leave it empty and let each user sign in from their own sandbox terminal. ## Native harness bridge servers Native harnesses (for example `claude-native`) run small HTTP servers on the sandbox host that the harness's hook and helper subprocesses call back into: - A **tool relay**, which **by default binds loopback only (`127.0.0.1`)**, so an ordinary host keeps it off every other interface. - An **MCP control ingress**, which by default listens on a Unix domain socket under the harness socket root (`/tmp/omnigent-/mcp-.sock`, mode `0600`) rather than a TCP port. It therefore needs no network policy, and a session costs no loopback listener for it — which matters under remote-dev port forwarders that mirror every TCP listener to your laptop and cap how many they will. Sockets left behind by a `serve-mcp` process that died without cleanup are reaped by owner pid on the next start. On Windows, the ingress always binds TCP from the port pool. A sandbox whose SSRF hardening denies loopback destinations unconditionally (the `openshell` provider is one such backend) cannot reach a loopback-advertised relay, so every prompt fails closed. Opt into an all-interfaces bind by setting `OMNIGENT_BRIDGE_BIND_HOST=0.0.0.0` in the sandbox host environment: both servers then listen on all interfaces (loopback consumers keep working) and advertise the host's routable address so the in-sandbox hooks can reach them. | Environment variable | Default | Description | | --------------------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `OMNIGENT_BRIDGE_BIND_HOST` | loopback-only relay, Unix socket for MCP ingress | Selects the bind posture. Unset is loopback-only (`127.0.0.1`) for the tool relay and a Unix domain socket for the MCP control ingress. `0.0.0.0` binds both servers on all interfaces and advertises the detected routable address (falling back to loopback when the host has none). Any other value pins that exact host for both bind and advertisement. Setting it to a nonblank value moves the MCP ingress onto a TCP port from the pool. | | `OMNIGENT_BRIDGE_PORT_POOL` | `28700`–`28715` | Overrides the server port pool. Accepts comma-separated ports and inclusive ranges, e.g. `28700-28703,29000`. | TCP-bound bridge servers draw their ports from a small stable pool (`28700`–`28715` by default) so a sandbox network policy can allowlist them by exact host and port. Several servers can coexist per host — one tool relay per session, plus the MCP ingress whenever `OMNIGENT_BRIDGE_BIND_HOST` is set — so allowlist the **whole** pool, not a single port. When the pool is exhausted the servers fall back to an OS-assigned ephemeral port, which an exact-port policy cannot cover. Every endpoint on these servers (other than `GET /health`) requires a per-relay bearer token, so allowlisting their host and ports does not expose unauthenticated functionality. ## Host configuration The provider-independent `host_config` block contains the `~/.omnigent/config.yaml` content installed inside each sandbox before `omnigent host` starts: ```yaml sandbox: provider: modal server_url: https://your-server.example.com host_config: providers: litellm: kind: gateway default: [pi] openai: base_url: http://litellm.litellm.svc.cluster.local/v1 api_key_ref: env:LITELLM_API_KEY wire_api: chat ``` On launch or resume, the server replaces or removes entries that it previously injected. Configuration created inside the sandbox under other names remains untouched. Do not put secrets in `host_config`. Use `api_key_ref: env:VAR`; the variable is resolved inside the sandbox from its provider-specific credential source. Inline `api_key` values are rejected. The server validates the block at startup. --- ## https://omnigent.ai/docs/reference/configuration/kubernetes # Kubernetes sandbox configuration The `kubernetes` managed sandbox provider starts a dedicated runner Pod for each managed session. Configure it under `sandbox.kubernetes` in the [server configuration](https://omnigent.ai/docs/reference/configuration). The `agent_sandbox` provider (self-reclaiming agent-sandbox `Sandbox` resources) reads the same `sandbox.kubernetes` settings and adds an optional `sandbox.agent_sandbox` block for [native warm pools](#native-warm-pools-for-agentsandbox-warmpool). See [Shared Server](https://omnigent.ai/docs/deploy/overview#kubernetes) for the deployment steps. ## Pod-start wait budget | Setting | Value | | ------- | ---------------------------------------- | | Key | `sandbox.kubernetes.pod_ready_timeout_s` | | Env var | `OMNIGENT_K8S_POD_READY_TIMEOUT_S` | | Type | Positive integer, in seconds | | Default | `90` | Before a managed session goes online, Omnigent waits for the runner Pod to schedule, pull its image, clone its repository (or several, in parallel, for a [multi-repo session](https://omnigent.ai/docs/deploy/cloud-sandbox-host#4-launch-from-the-web-ui)), and reach `Running`. Increase the wait budget when cold image pulls regularly take longer than the default: ```yaml sandbox: provider: kubernetes kubernetes: pod_ready_timeout_s: 300 ``` The budget covers Pod discovery, replacement Pod discovery after an eviction or node drain, Kubernetes API read errors, and time spent `Pending`. When it expires, the launch error reports the configured deadline and last observed Pod state. The value must be a positive YAML integer. Values such as `0`, `"90"`, and `true` fail server startup. To shorten the clone step itself, set the provider-independent [`sandbox.git_clone`](https://omnigent.ai/docs/reference/configuration/cloud-sandbox#git-clone-options) block (shallow depth, single-branch, blobless filter). The Kubernetes provider honors it for every repository it clones, including warm-pool Pods. ## Sandboxed container runtime (`runtime_class`) | Setting | Value | | ------- | ------------------------------------- | | Key | `sandbox.kubernetes.runtime_class` | | Type | String (DNS-1123 subdomain) | | Default | Unset — the cluster's default runtime | Set `runtime_class` to schedule every runner Pod onto a sandboxed container runtime your cluster exposes through a `RuntimeClass` object — for example [Kata Containers](https://katacontainers.io/) micro-VMs or [gVisor](https://gvisor.dev/). Omnigent writes the value verbatim to the Pod's `spec.runtimeClassName`: ```yaml sandbox: provider: kubernetes kubernetes: runtime_class: kata ``` Leaving `runtime_class` unset keeps the Pod on the cluster's default runtime — today's behaviour, byte-for-byte. The name must be a DNS-1123 subdomain and must match a `RuntimeClass` you pre-created in the cluster; a malformed value (for example `Not_A_DNS_Name`) fails server startup rather than the first launch. Omnigent only references the `RuntimeClass` — provisioning it and the underlying node runtime is an out-of-band operator step. ## Scheduling onto a tainted NodePool (`tolerations`) | Setting | Value | | ------- | ---------------------------------------------------------------- | | Key | `sandbox.kubernetes.tolerations` | | Type | List of `{key?, operator?, value?, effect?, tolerationSeconds?}` | | Default | Unset — no tolerations added | Set `tolerations` to let runner Pods schedule onto a tainted NodePool you've dedicated to sandboxes. Each entry is a Kubernetes [Toleration](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/), written verbatim to the Pod's `spec.tolerations`: ```yaml sandbox: provider: kubernetes kubernetes: tolerations: - key: sei.io/node-role operator: Equal value: omnigent-sandbox effect: NoSchedule node_selector: sei.io/node-role: omnigent-sandbox ``` A toleration only **permits** the Pod to schedule onto matching tainted nodes — it does not attract it there. Pair `tolerations` with `node_selector` to also pin runner Pods onto that pool; without a selector the Pod may still land on any untainted node. Leaving `tolerations` unset adds no `spec.tolerations` key, keeping the manifest byte-for-byte compatible with prior deployments. Each entry accepts these fields: - **`key`** — the taint key to tolerate. Must be a valid Kubernetes label key. Omit it only with `operator: Exists` (Kubernetes' "tolerate everything" form). - **`operator`** — `Equal` (default) or `Exists`. `Exists` matches any value, so it may not be combined with a `value`. - **`value`** — the taint value, matched when `operator: Equal`. Must be a valid Kubernetes label value. - **`effect`** — one of `NoSchedule`, `PreferNoSchedule`, or `NoExecute`. Omit to tolerate the key under every effect. - **`tolerationSeconds`** — an integer, valid only with `effect: NoExecute`; how long the Pod stays bound to a node after a matching taint is added. Entries are validated at server startup, so a malformed or self-contradictory toleration (an `Exists` operator with a `value`, an empty `key` with an operator other than `Exists`, or a `tolerationSeconds` without `effect: NoExecute`) fails fast rather than at the first managed launch. ### Setting the budget from the environment You can also set the wait budget with the `OMNIGENT_K8S_POD_READY_TIMEOUT_S` environment variable, which is handy when the same bundle runs across deployments with different image-pull times: ```bash export OMNIGENT_K8S_POD_READY_TIMEOUT_S=300 ``` Precedence: the explicit `sandbox.kubernetes.pod_ready_timeout_s` config key always wins when set; the env var is only consulted when that key is unset, and it overrides the built-in `90`-second default. A non-numeric env value (for example `not-a-number`) fails fast with a clear error; a float-looking value such as `120.0` is accepted and truncated to a whole number of seconds. ## Persistent storage for runner Pods Runner Pods are ephemeral: the workspace lives on an `emptyDir` and is destroyed with the Pod. To expose durable data (datasets, model caches, shared output directories), mount pre-created PersistentVolumeClaims into every runner Pod with `sandbox.kubernetes.pvc_mounts`: ``` sandbox: provider: kubernetes kubernetes: pvc_mounts: - claim_name: omnigent-datasets mount_path: /mnt/datasets read_only: true # default true; set false only for shared scratch ``` Each entry is a `{claim_name, mount_path, read_only?}` mapping. `read_only` defaults to `true`. Omnigent only **references** the claim — you pre-create the PV/PVC in the runner namespace (`omnigent-sandboxes`) out of band, so the server RBAC stays unchanged. Mounts land on the host container only; the init container that clones the repo never sees them. Keep these caveats in mind: - **Mount paths are validated at server startup.** A `mount_path` may not overlap `/home/omnigent`, Secret projections, or the image's OS directories (e.g. `/home`, `/var`, `/run`, `/tmp`, `/etc`, `/opt`); a malformed or reserved path fails startup rather than the first launch. Paths must be absolute and normalized, and may not duplicate or nest within each other. - **Writable claims are shared across concurrent runners.** Use a `ReadWriteMany`-capable backend (NFS/SMB/CephFS) for anything writable, and prefer `read_only: true` everywhere else — a writable shared mount lets one session read and modify what another wrote, and its contents outlive the Pod. - **`ReadWriteOnce` claims pin all runners to one node** — combine with `node_selector` deliberately, or later Pods sit `Pending`. - A mount visible in the Pod is **not** automatically visible to a harness's own OS-level sandbox ([Omnibox](https://omnigent.ai/docs/omnibox) path grants are separate). See `deploy/kubernetes/README.md` and `deploy/kubernetes/overlays/sandbox-runners/README.md` in the repo for the full guide. ## Rotation-friendly credentials (`secret_mounts`) Under `sandbox.kubernetes`, an optional `secret_mounts` list projects Kubernetes Secrets you pre-created in the runner namespace as read-only files on each runner Pod. Each entry is a `{ secret_name, mount_path }` mapping: ``` sandbox: provider: kubernetes server_url: https://your-server.example.com kubernetes: secret_mounts: - secret_name: git-token # a Secret in the runner namespace mount_path: /mnt/secrets/git # absolute, non-reserved in-Pod path - secret_name: npm-token mount_path: /mnt/secrets/npm ``` Unlike credentials injected via `envFrom` (read once at container start), a Secret projected as a volume is refreshed in place by the kubelet, so a long-lived runner picks up a **rotated** credential without a restart. The refresh is eventually consistent (kubelet sync, up to ~1 min), so the in-sandbox consumer must re-read the file on each use — a value cached at start defeats the rotation. Each volume mounts read-only on the host container only (never the clone-time init container), so a Secret volume has no `read_only` knob. Entries are validated at server startup: `secret_name` must be a DNS-1123 subdomain and `mount_path` an absolute, normalized path that isn't reserved and doesn't duplicate, nest with, or overlap another `secret_mounts` or `pvc_mounts` path — an operator typo fails startup, not the first launch. A missing Secret keeps the Pod from starting. ## Native warm pools for `agent_sandbox` (`warm_pool`) | Setting | Value | | -------- | ------------------------------------------------------------- | | Key | `sandbox.agent_sandbox.warm_pool` | | Type | String (DNS-1123 subdomain) — the name of a `SandboxWarmPool` | | Default | Unset — every session creates its `Sandbox` directly | | Requires | `sandbox.provider: agent_sandbox` | With the `agent_sandbox` provider, a warm pool prepares spare Pods **before** a session asks for one. Image pulls, scheduling, container startup, and initial PVC provisioning happen ahead of the request; Omnigent then claims a compatible `Sandbox`, delivers the managed-host identity to the waiting bootstrap, and runs the normal repository preparation and host startup. GitHub and Databricks credentials still come from the existing owner-bound broker and credential store, and the suspend/resume lifecycle with a durable HOME PVC is unchanged. Opt in by naming the operator-owned pool: ```yaml sandbox: provider: agent_sandbox server_url: http://omnigent.omnigent.svc.cluster.local:8000 agent_sandbox: warm_pool: omnigent-default-v1 kubernetes: namespace: omnigent-sandboxes service_account: omnigent-runner image: your-registry/omnigent-host:your-version ``` Validation happens at server startup: `warm_pool` is the only key allowed under `sandbox.agent_sandbox`, the value must be a non-empty DNS-1123 subdomain (surrounding whitespace is trimmed), and setting `sandbox.agent_sandbox` with any other `provider` fails with `requires provider: agent_sandbox`. Omitting the block, or setting `warm_pool: null`, keeps direct `Sandbox` creation. The key also works inside a `providers:` list entry in a multi-provider deployment. ### Prerequisites - The upstream agent-sandbox **v1.0.0** controller **with extensions** — the `SandboxTemplate`, `SandboxWarmPool`, and `SandboxClaim` CRDs (`extensions.agents.x-k8s.io/v1beta1`): ```bash kubectl --context YOUR_CONTEXT apply --server-side -f \ https://github.com/kubernetes-sigs/agent-sandbox/releases/download/v1.0.0/sandbox-with-extensions.yaml ``` - A host image built from the **same Omnigent revision as the server**. Warm Pods run the `omnigent.host.warm_bootstrap` module, which must be present in the image. - The additive warm-pool RBAC overlay (see below). ### Generate the template and pool Omnigent reads the pool but never creates, resizes, or updates it — the `SandboxTemplate` and `SandboxWarmPool` are operator-owned. Render them from the **exact** server config you deploy, with the same workspace-volume environment settings the server uses, so the template's profile matches what the server expects: ```bash export OMNIGENT_AGENT_SANDBOX_WORKSPACE_SIZE=5Gi python -m omnigent.onboarding.sandboxes.agent_sandbox_warm_pool \ --config server-config.yaml --name omnigent-default-v1 --replicas 2 --shared \ > warm-pool.yaml kubectl --context YOUR_CONTEXT apply -f warm-pool.yaml kubectl --context YOUR_CONTEXT apply -k deploy/kubernetes/overlays/sandbox-runners/warm-pool ``` Generator options: - **`--config PATH`** (required) — the server config; it must enable `sandbox.provider: agent_sandbox`. - **`--name NAME`** (required) — used for both the `SandboxTemplate` and the `SandboxWarmPool`; set `sandbox.agent_sandbox.warm_pool` to the same value. - **`--replicas N`** — spare Pods to keep ready (default `1`, must be non-negative). - **`--shared`** or **`--agent-name NAME`** — mutually exclusive; they choose which sessions the pool may serve (see the table below). The output is a `SandboxTemplate` with `networkPolicyManagement: Unmanaged` and a `SandboxWarmPool` using the `Recreate` update strategy, which only replaces **unused** inventory. Use a new versioned pool name (e.g. `…-v2`) whenever you change the mode or any profile setting so old spare Pods never serve the previous profile. | Generator option | Sessions the pool serves | | ---------------- | ------------------------------------------------------------------------------------------------------------------ | | `--shared` | Any session harness or trusted agent classifier, including uploaded agents, with the same infrastructure profile. | | `--agent-name` | Only the exact built-in agent classifier; other classifiers fall back to direct provisioning. | | Neither | Legacy unclassified behaviour: only an exact empty-classifier match uses the pool; built-in classifiers go direct. | `--shared` is recommended for deployments using the owner-bound GitHub and Databricks brokers, so built-in and uploaded agents share the same spare inventory. A shared template omits the `omnigent.ai/agent` label and carries the annotation `omnigent.ai/warm-pool-shared: "true"`; the marker is part of the profile fingerprint, so an existing pool that merely lacks the agent label is **not** shared. Per-agent admission-time credential injection does not apply to an unlabeled shared pool — use `--agent-name` if you rely on it. Shared mode changes pool compatibility only; credentials stay scoped to the host's owner. ### RBAC overlay `deploy/kubernetes/overlays/sandbox-runners/warm-pool` adds a namespaced `Role`/`RoleBinding` for the `omnigent-server` ServiceAccount in `omnigent-sandboxes`: - `sandboxclaims`: `create`, `get`, `patch`, `delete` - `sandboxtemplates`, `sandboxwarmpools`: `get` - `pods/exec`: `create`, `get` — used to deliver the session identity to the bootstrap after allocation Kubernetes RBAC cannot restrict `exec` to a particular command, so enabling warm mode is an explicit permission expansion for the server within the runner namespace. Runner ServiceAccounts receive none of these grants, and direct mode does not need the overlay. ### Behaviour and caveats - **Allocated profiles stay fixed.** An allocated warm `Sandbox` keeps its original image, mounts, resources, scheduling, security settings, and agent classifier. Changing those server settings can block a later wake of that session; the mismatch fails with a profile error and preserves the `Sandbox` and its PVC. Shared allocations tolerate agent or harness changes across suspension when the infrastructure profile still matches; dedicated and legacy unclassified allocations require an exact classifier match. - **Changing `warm_pool` affects new allocations only.** Existing claims and workspaces are not migrated, and disabling pooling does not convert existing warm allocations into direct ones. Use direct provisioning for sessions that need infrastructure changes across wake. - **Capacity planning counts both containers.** The bootstrap and host run as regular containers and each reserves the configured resources, so `250m` CPU / `384Mi` memory in the config reserves `500m` / `768Mi` per pooled Pod (before injected sidecars). Size nodes for spare **and** allocated Pods. - **Networking is yours.** Generated templates set `networkPolicyManagement: Unmanaged` and use cluster DNS, matching the direct provider. Provide production `NetworkPolicy` objects yourself, including DNS and egress to the Omnigent callback URL and any private Git/Databricks endpoints. - **Do not customise individual claims.** Adding environment variables or PVC overrides to a `SandboxClaim` makes upstream create a cold `Sandbox` instead. Session-specific data arrives after allocation; each template-created HOME PVC belongs to its `Sandbox` and survives suspension. Allocated Sandboxes are never returned to the spare pool. - **An empty pool still works.** Upstream may create a cold member from the same template, so a successful allocation alone does not prove a warm hit — compare Pod UIDs recorded before the request when measuring latency. - Static harness Secrets injected via `envFrom` are read when a spare Pod starts, so rotate them by replacing unused Pods before relying on the new value. ### Verify a deployment An opt-in live end-to-end test exercises allocation, host/runner registration, workspace persistence, pool replenishment, and suspend/wake against a real cluster. It needs a test server that accepts API requests without an authentication header (such as a single-user development server), a shared pool with at least one ready spare in a namespace isolated from other launchers, and a host image that supports the Claude SDK harness. Without `OMNIGENT_WARM_POOL_E2E=1` the test skips; no model or connected-account credentials are required. ```bash OMNIGENT_WARM_POOL_E2E=1 \ OMNIGENT_WARM_POOL_SERVER_URL=http://127.0.0.1:8000 \ OMNIGENT_WARM_POOL_KUBECONFIG=/absolute/path/to/test-kubeconfig \ OMNIGENT_WARM_POOL_CONTEXT=YOUR_TEST_CONTEXT \ OMNIGENT_WARM_POOL_NAMESPACE=omnigent-sandboxes \ OMNIGENT_WARM_POOL_NAME=omnigent-default-v1 \ .venv/bin/python -m pytest tests/e2e/test_agent_sandbox_warm_pool.py -v ``` Validate credential behaviour separately in an authenticated deployment: connect test GitHub and Databricks accounts, create a pooled session with a private repository, and check `git fetch` and `gh api user --jq .login`; confirm the workspace survives suspend and wake and that both services still authenticate afterwards. Keep static provider credential Secrets out of the pool during this check so they cannot mask a failed broker lookup. See `deploy/kubernetes/overlays/sandbox-runners/warm-pool/README.md` in the repo for the full runbook. --- ## https://omnigent.ai/docs/reference/configuration/os-sandbox # OS sandbox configuration Omnibox is Omnigent's secure OS sandbox for any agent. On Linux and macOS, it restricts filesystem and network access at the OS level, and it hides credentials from the agent and brokers access to them. Run agents with minimal permissions, or lock them down for unattended YOLO-mode execution. The OS sandbox restricts what commands and file operations your agent can perform. It controls which files the agent can read and write, whether it can access the network, and which environment variables it sees. This is different from the [cloud sandbox host](https://omnigent.ai/docs/deploy/cloud-sandbox-host), which controls _where_ the runner executes. The OS sandbox controls _what the agent can access_, regardless of where it runs. The OS sandbox applies to the built-in OS tools (`sys_os_read`, `sys_os_write`, `sys_os_edit`, `sys_os_shell`) and any terminals you declare in the agent config. **Requirements:** Linux: install bubblewrap (`apt install bubblewrap` or `dnf install bubblewrap`). macOS: `sandbox-exec` ships with stock macOS. If you ask for a sandbox and the backend isn't available, Omnigent errors rather than running unsandboxed. Windows: job objects provide process-tree cleanup for OS-tool helpers; they don't restrict filesystem or network access. For full sandboxing on Windows, run Omnigent under WSL or use a [cloud sandbox host](https://omnigent.ai/docs/deploy/cloud-sandbox-host). ## Minimal config The smallest useful OS sandbox. Make the working directory writable and let Omnigent pick the backend for your platform: ``` os_env: type: caller_process cwd: . sandbox: write_paths: [.] # cwd is read-only by default; opt it back in allow_network: true ``` On Linux, Omnigent uses bubblewrap (`bwrap`). On macOS, it uses Seatbelt (`sandbox-exec`). Omit `type` — or set `type: auto` — to pick the platform default; the two resolve identically. | Platform | Backend | Mechanism | | -------- | ------------------- | -------------------------------- | | Linux | `linux_bwrap` | Bubblewrap namespaces + seccomp | | macOS | `darwin_seatbelt` | `sandbox-exec` SBPL profiles | | Windows | `windows_jobobject` | Job objects (process cleanup) | | Other | `none` | No sandboxing (explicit opt-out) | ### Choosing or disabling the backend You usually don't set `type` at all — Omnigent picks the platform default. Two explicit values are available when you want to be deliberate: - `type: auto` requests the platform default explicitly. It is identical to omitting `type`, so the same YAML stays portable across platforms. - `type: none` and `type: null` both explicitly disable the sandbox. Setting the `sandbox:` block to `false` is equivalent. Asking for a named backend (for example `type: linux_bwrap`) still routes to that backend and errors loudly on a platform that can't provide it — the author asked for it by name. ## What you can restrict ### Filesystem By default, `cwd` is read-only on hardened backends. You opt in to writes explicitly. ``` sandbox: read_paths: [~/.gitconfig, ~/.ssh] # read-only access outside cwd write_paths: [.] # writable directories write_files: [~/.ssh/known_hosts] # individual writable files cwd_allow_hidden: [.venv, .git, .env] # dotfiles to allow (rest are masked) mask_paths: [config/production.key] # hide specific paths by name/depth ``` Dotfiles under `cwd` and `read_paths` are hidden by default unless listed in `cwd_allow_hidden`. This makes broad read grants safe: granting `~` doesn't expose `~/.aws/credentials` or `~/.ssh/id_rsa`. On macOS, `~/Library` is also denied by default. By default the dotfile mask is **non-recursive** — it scans only the top level of `cwd` and each `read_paths` root. Top-level secrets (`.env`, `.git/`, `.aws/`, `.ssh/`) are still hidden, but a dotfile nested below the first level (for example `services/api/.env`) stays visible. This is the scalable default: a recursive walk of a medium or large tree visits enormous numbers of entries and routinely trips `cwd_hidden_scan_max_entries`. For untrusted source trees where a deeply nested credential file would be an unacceptable leak, set `cwd_hidden_scan_recursive: true` to walk the whole tree. The `cwd_hidden_scan_max_entries` and `cwd_hidden_scan_overflow` knobs then bound the cost of the full walk. ``` sandbox: cwd_hidden_scan_recursive: true # mask dotfiles at any depth ``` To hide a specific file or directory the name-based masker won't catch — a named secret like `config/production.key`, or a single deeply nested dotfile without turning on full recursion — list it under `mask_paths`. Entries are resolved like `read_paths` (`~` is expanded, relative paths are taken against `cwd`, `$VAR` is not expanded) and are hidden regardless of name or depth, on top of the dotfile mask. A directory is masked as an empty view, a file as an empty file. ``` sandbox: mask_paths: - config/production.key - ~/.config/private ``` ### Disposable copy-on-write writes (Linux) A `write_paths` entry can also be an object with `copy_on_write: true`. On the `linux_bwrap` backend, that directory is mounted as a disposable overlay: the agent sees it at its original path and can create, edit, rename, and delete files there, but the host's original files never change. A plain string entry is shorthand for `{path: ..., copy_on_write: false}`. ``` executor: harness: openai-agents os_env: type: caller_process cwd: . sandbox: type: linux_bwrap write_paths: - ./artifacts # persistent: writes land on the host - path: ./dependencies copy_on_write: true # disposable: writes stay in the overlay terminals: bash: command: bash os_env: inherit ``` Create `artifacts` and `dependencies` before starting the session. The `sys_os_*` tools, shell commands, and inherited terminals all share the same overlay, including across turns and terminal reopen. Writes to `artifacts` persist normally. The disposable view belongs to the session's OS environment: - Its changes are discarded when that environment closes, and they are not restored after a runner restart. Separate sessions get separate views. Export anything you want to keep into a persistent write path before the session ends. - Overlay data lives in temporary memory-backed storage and consumes memory/swap as files are copied up. - The original directory should stay unchanged on the host while its overlay is active; OverlayFS does not snapshot concurrent host edits. Copy-on-write is currently restricted, and Omnigent rejects configs that break these rules rather than falling back to writing the original directory: - Requires `sandbox.type: linux_bwrap` and `executor.harness: openai-agents`. Other harnesses are rejected because their native tools do not yet share the session's mount namespace. - Each copy-on-write path must name an existing directory. - Nested copy-on-write roots, persistent `write_paths` inside a copy-on-write root, and `write_files` inside one are rejected. A persistent parent with a copy-on-write child is supported. - `os_env.fork` and sandbox overrides are not supported with copy-on-write paths. - In a copy-on-write session, file uploads, agent bundle downloads (`sys_agent_download`), local agent config discovery (`sys_agent_list`), skill loading/reading, and `sys_session_create` with `config_path` return an explicit unsupported-operation error. Use `sys_os_*` tools and an inherited terminal for filesystem work, and export results to a persistent write path. #### Host requirements These apply only to paths with `copy_on_write: true`; ordinary `read_paths` and persistent `write_paths` keep their existing requirements. They are checked on the machine running the sandbox, not the UI: - **Bubblewrap 0.11+**, providing `--overlay-src` and `--tmp-overlay`. - **Unprivileged OverlayFS mounts with `userxattr` support** — upstream Linux **5.11+**; some distributions backport them to older kernels. - **tmpfs `user.*` extended attributes** (upstream **Linux 6.6**) for Bubblewrap's temporary upper layer. The upstream baseline is therefore **6.6+**, or distro backports of both features. Older tmpfs can allow the mount but fail later directory operations. - **Kernel facilities:** user/mount namespaces (`CONFIG_NAMESPACES`, `CONFIG_USER_NS`), OverlayFS (`CONFIG_OVERLAY_FS`, built in or as a module), and tmpfs with extended attributes (`CONFIG_TMPFS`, `CONFIG_TMPFS_XATTR`). - **Permission to use them:** sufficient `user.max_user_namespaces` and `user.max_mnt_namespaces` quotas, `kernel.unprivileged_userns_clone=1` where that switch exists, and AppArmor/SELinux/seccomp/container policy that allows namespace creation and OverlayFS mounts. Ubuntu 24.04's AppArmor restrictions, for example, can block unprivileged user namespaces even on a recent kernel. Omnigent does not change host security settings. Omnigent attempts the real mounts and tests tmpfs user extended attributes when it initializes a copy-on-write environment — it neither rejects a working distro backport based on `uname` nor accepts a host solely for having a new kernel. On failure the error reports the running kernel, these prerequisites, and Bubblewrap's original message; the session fails before running tools in that environment. A missing or unlaunchable `bwrap` binary and a mount startup timeout also produce copy-on-write-specific errors. To check support, run this as the same user and inside the same container/VM as the runner, from a directory containing an existing `dependencies` folder: ``` uname -r bwrap --version cow_probe_dir=$(mktemp -d) bwrap --unshare-user --ro-bind / / \ --overlay-src "$PWD/dependencies" --tmp-overlay "$PWD/dependencies" \ --tmpfs "$cow_probe_dir" \ -- python3 -c 'import os,sys,tempfile; f=tempfile.TemporaryFile(dir=sys.argv[1]); os.setxattr(f.fileno(), "user.cow_probe", b"1")' "$cow_probe_dir" cow_probe_status=$? rmdir "$cow_probe_dir" test "$cow_probe_status" -eq 0 ``` The probe should exit successfully without changing `dependencies`. An unknown overlay option indicates an old Bubblewrap build; a namespace or mount permission error points at host policy; an unsupported filesystem/option points at missing kernel support. See the [Bubblewrap manual](https://github.com/containers/bubblewrap/blob/v0.12.0/bwrap.xml), the [kernel OverlayFS documentation](https://docs.kernel.org/filesystems/overlayfs.html), and the [Linux 6.6 tmpfs extended-attribute handlers](https://github.com/torvalds/linux/blob/v6.6/mm/shmem.c#L3711). ### Network ``` sandbox: allow_network: true # basic on/off egress_rules: # optional HTTP(S) allow-list - "GET api.github.com/repos/myorg/**" # GET only, one org - "* pypi.org/**" # any method - "* *.github.com/**" # wildcard subdomain ``` When `egress_rules` is set, all HTTP(S) traffic goes through a MITM proxy with default-deny. Only requests matching a rule are allowed. Requires a hardened backend (`linux_bwrap` or `darwin_seatbelt`). Each rule is `"METHODS host/path-glob"`: comma-separated HTTP verbs (or `*` for any), a hostname (or `*.domain` for subdomains), and a path glob where `**` matches any depth. By default, the proxy also blocks connections to private IPs (RFC1918, loopback, cloud metadata like `169.254.169.254`). Set `egress_allow_private_destinations: true` if your agent needs to reach internal services. `egress_rules` covers HTTP(S) only. To let a sandboxed `git` reach a repository over SSH without handing it the private key, use [`git_ssh`](#git-over-ssh) — it isolates the sandbox network the same way `egress_rules` does, and the two can be combined. ### Environment ``` sandbox: env_passthrough: [GH_TOKEN, AWS_PROFILE] # only these vars reach the agent ``` Agent CLIs are spawned with a **deny-by-default** environment. Only two things are inherited automatically: the shared base (`HOME`, `PATH`, proxy/TLS settings, locale, `TMPDIR`, and the Omnigent session marker) and the running harness's own variable family. Everything else — including a host secret that belongs to some other provider — is stripped, and only reaches the agent if you name it in `env_passthrough`. An ambient key that used to reach the CLI implicitly now has to be declared. Two cases are worth knowing: - **A generic ACP agent** (`type: acp`) gets no vendor family at all, because Omnigent cannot know which vendor an arbitrary agent belongs to. Gemini via ACP needs `env_passthrough: ["GEMINI_API_KEY"]`. - **Goose** owns `GOOSE_*` only. Goose configured through Omnigent's provider/gateway routing is unaffected, but a goose set up against an ambient provider key (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, …) needs that name declared here. If an agent fails during its `initialize` handshake for no obvious reason, check this field first — the ACP executor logs a hint pointing back at it. ### Credentials (secretless proxy) `credential_proxy` lets sandboxed tools authenticate to external hosts without the real secret ever entering the sandbox: the mandatory L7 egress proxy attaches the credential on the way out, swapping a synthetic placeholder for the real value. It requires `egress_rules` and a hardened, network-isolating backend (`linux_bwrap` or `darwin_seatbelt`). Each entry has a `type`. The host-keyed types — `https_bearer`, `https_basic`, `git_https`, and `gh_basic` — bind a credential to a `target`/`targets` host and resolve the secret from a `source`. ``` sandbox: egress_rules: - "* api.github.com/**" credential_proxy: - type: https_bearer target: api.github.com source: { env: GH_TOKEN } ``` #### Databricks CLI The `databricks_cli` type proxies the [Databricks CLI](https://docs.databricks.com/aws/en/dev-tools/cli/). Unlike the host-keyed types, it is **profile-keyed**: list the `profiles` to proxy (and an optional `default`). Only the listed profiles are materialized into the sandbox — as a placeholder `.databrickscfg` whose tokens are synthetic `oa_cred_*` values — and swapped by the proxy; every other profile is invisible to the agent. Omnigent points `DATABRICKS_CONFIG_FILE` at the placeholder file, and sets `DATABRICKS_CONFIG_PROFILE` when `default` is given. ``` os_env: type: caller_process cwd: . sandbox: type: linux_bwrap egress_rules: - "* pypi.org/**" # your other egress needs - "* dbc-adb7b1a3-9097.cloud.databricks.com/**" # the proxied workspace credential_proxy: - type: databricks_cli profiles: [dbc-adb7b1a3-9097, oss] default: dbc-adb7b1a3-9097 # optional; sets DATABRICKS_CONFIG_PROFILE ``` Inside the sandbox, `databricks --profile dbc-adb7b1a3-9097 current-user me` works; the sandbox holds only `oa_cred_*` placeholders, never a live token. As with every credential-proxy type, you must list each workspace host in `egress_rules` yourself — the proxy never widens egress on its own. OAuth profiles (`auth_type = databricks-cli`) are refreshed for the life of the session, so long sessions survive the ~1h token expiry. `databricks_cli` requires the `databricks` extra (`pip install omnigent[databricks]`) and only works on `linux_bwrap` — the Go CLI ignores `SSL_CERT_FILE` on macOS, so `darwin_seatbelt` is rejected. ### Git over SSH `git_ssh` grants a sandboxed `git` access to **one repository and one SSH operation at a time** without exposing the SSH private key. The trusted parent process runs OpenSSH with the key; the sandbox only receives a Git transport helper (`GIT_SSH_COMMAND`) and a private Unix socket. It cannot read the key or connect to the SSH host directly. Use a read-only deploy key for `fetch` when the server supports one. ``` os_env: type: caller_process cwd: . sandbox: type: auto # resolves to linux_bwrap or darwin_seatbelt git_ssh: - host: github.com port: 22 username: git repository: my-org/my-repo.git operations: [fetch] identity_file: /home/runner/.ssh/my-repo-deploy-key known_hosts_file: /home/runner/.ssh/github-pinned-host-key ``` Inside the sandbox, `git clone git@github.com:my-org/my-repo.git` and `git fetch` work as usual, from `sys_os_shell` and from sandboxed terminals alike. The binding only matches an exact `username@host`, port, and repository path, so add a separate binding for each submodule or additional repository. Add `push` to `operations` only for a write-capable key. - `known_hosts_file` must contain one exact host key entry for `host`, for example `github.com ssh-ed25519 `. Record the expected key through a trusted channel; don't accept an unverified `ssh-keyscan` result. - `identity_file` and `known_hosts_file` take literal absolute paths — `~` is **not** expanded. Keep both files outside the sandbox's read and write paths; Omnigent protects them from the sandbox like other credential sources. - The parent resolves DNS for each connection and every returned address must be allowed. Public addresses need no extra setting. For a private SSH server, set `allowed_cidrs` on that binding to the narrow range containing its addresses. The parent pins the chosen address for the SSH connection and verifies the host key against the configured `host`. - Loopback stays blocked unless a local-development binding uses a loopback IP literal as `host`, its exact `/32` CIDR in `allowed_cidrs`, and `allow_loopback: true` together. Don't use that for a shared or production SSH server. - The grant covers Git's `git-upload-pack` (`fetch`) and, when explicitly allowed, `git-receive-pack` (`push`). It does not grant a shell, arbitrary SSH commands, or Git LFS HTTP authentication. `git_ssh` requires `linux_bwrap` or `darwin_seatbelt`; other backends reject the setting. The host running the sandbox needs the OpenSSH client at `/usr/bin/ssh`. Like `egress_rules`, `git_ssh` isolates the sandbox network — only the broker socket (and the egress proxy, if `egress_rules` is also set) is reachable — and it is incompatible with `allow_sandbox_override: true` on a terminal, since an override to `none` would drop the enforcement. ## Sharing a policy Declare the sandbox once and reuse it with a YAML anchor: ``` os_env: type: caller_process cwd: . sandbox: &shared write_paths: [.] read_paths: [~/.gitconfig, ~/.ssh] allow_network: true terminals: zsh: command: zsh os_env: type: caller_process cwd: . sandbox: *shared # same policy as sys_os_* tools ``` Or use `os_env: inherit` on a terminal or sub-agent to inherit the parent's full environment including its sandbox. In a multi-harness setup, each sub-agent defines its own sandbox in its own `config.yaml` file in the `agents/` subdirectory. Agent entries in `tools.agents` are just names (strings), not inline config blocks. ``` # Parent config.yaml tools: agents: - researcher - coder # agents/researcher/config.yaml os_env: sandbox: write_paths: [./research] allow_network: true # agents/coder/config.yaml os_env: sandbox: write_paths: [./src] allow_network: false ``` ## What is and isn't sandboxed The OS sandbox applies to `sys_os_*` tool calls and terminals that reference the policy. It does **not** apply to: - **MCP servers.** The runner spawns MCP subprocesses outside the sandbox. Constrain an MCP server at its own configuration site. - **The Omnigent supervisor process.** It runs the model loop and dispatches tools. Only the commands it issues through OS tools run inside the sandbox. If you ask for a sandbox (explicitly or via the default) and it can't be provided, Omnigent errors instead of quietly running unsandboxed. The only opt-out is `sandbox.type: none` (equivalently `sandbox.type: null` or `sandbox: false`). ## Field reference ### `os_env` | Field | Type | Default | Description | | ------------------ | ------ | ---------------- | ----------------------------------------------------------------------------------------------------------- | | `type` | string | `caller_process` | OS environment backend | | `cwd` | string | `.` | Working directory | | `sandbox` | block | platform default | Sandbox policy (see below) | | `start_in_scratch` | bool | `false` | Start in a writable scratch tmpdir instead of `cwd`. Workspace bound read-only. Requires an active sandbox. | ### `os_env.sandbox` | Field | Type | Default | Description | | ----------------------------------- | -------------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `type` | string | platform default | `auto` (or omitted) picks the platform default; `linux_bwrap`, `darwin_seatbelt`, `windows_jobobject`; `none`/`null` disables | | `write_paths` | (string or object)[] | `[]` | Writable directories. `cwd` is read-only by default. An object entry takes `path` and `copy_on_write` (bool, default `false`); see [copy-on-write writes](#disposable-copy-on-write-writes-linux). | | `write_files` | string[] | `[]` | Individual writable files | | `read_paths` | string[] | none | Read-only grants outside `cwd` | | `allow_network` | bool | `true` | Network access on/off | | `cwd_allow_hidden` | string[] | `[".venv"]` | Dotfile basenames to allow | | `cwd_hidden_scan_recursive` | bool | `false` | Recurse subdirectories when masking dotfiles. `false` scans top level only. | | `mask_paths` | string[] | none | Explicit files/dirs to hide by any path/name, on top of the dotfile mask | | `cwd_hidden_scan_max_entries` | int | `50000` | Max entries for dotfile mask walk (recursive mode) | | `cwd_hidden_scan_overflow` | string | `warn` | `error`, `warn`, or `unlimited` | | `env_passthrough` | string[] | minimal set | Env vars the agent can see | | `egress_rules` | string[] | none | HTTP(S) allow-list. Default-deny when set. | | `egress_allow_private_destinations` | bool | `false` | Allow connections to private/metadata IPs | | `credential_proxy` | block[] | none | Secretless auth entries; requires `egress_rules` | | `git_ssh` | block[] | none | Repository-scoped [Git-over-SSH](#git-over-ssh) bindings brokered by the parent; requires `linux_bwrap` or `darwin_seatbelt` | ### `os_env.sandbox.git_ssh[]` Each entry binds one SSH identity to one repository on one destination. Entries with the same `host`, `port`, `username`, and `repository` are rejected as duplicates. | Field | Type | Default | Description | | ------------------ | -------- | --------- | -------------------------------------------------------------------------------------------------------------- | | `host` | string | required | Exact ASCII DNS name or IPv4 address of the SSH server | | `port` | int | `22` | SSH port (1–65535) | | `username` | string | `git` | SSH user | | `repository` | string | required | Repository path as Git sends it, e.g. `my-org/my-repo.git` | | `operations` | string[] | `[fetch]` | `fetch` and/or `push` | | `identity_file` | string | required | Absolute path to the private key; `~` is not expanded | | `known_hosts_file` | string | required | Absolute path to a file with one exact host key entry for `host`; `~` is not expanded | | `allowed_cidrs` | string[] | `[]` | Address ranges the resolved host must fall in. Empty allows only public addresses. | | `allow_loopback` | bool | `false` | Permit a loopback destination. Requires a loopback IP literal `host` and exactly its `/32` in `allowed_cidrs`. | --- ## https://omnigent.ai/docs/reference/configuration/routing # Smart Routing configuration Smart Routing selects a harness and model from the session's first message. See [Smart Routing](https://omnigent.ai/docs/build/routing) for the user workflow and behavior. ## Built-in routing Configure a server-level `llm:` block to use the built-in judge. No `routing:` block is required. With neither an `llm:` block nor an external `routing:` block, Smart Routing is disabled and the **Auto** picker option is hidden. ```yaml llm: model: openai/gpt-5.5 connection: api_key: ${OPENAI_API_KEY} ``` | Field | Required | Description | | ----------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `model` | Yes | Provider-prefixed model id used by the judge, such as `openai/gpt-5.5`. | | `connection` | No | Provider-specific connection overrides, such as `api_key` and `base_url` for an OpenAI-compatible endpoint. `${ENV}` references are expanded. Omit it to use the provider's usual environment variables. | | `profile` | No | Databricks CLI profile from `~/.databrickscfg`. `connection` takes precedence when both are set. | | `fallback_models` | No | Ordered provider-prefixed models to try if the primary model fails. The same connection is shared across the list, so use same-provider fallbacks or environment-based credentials. | The built-in judge caps the entire routing call at 9 seconds, including fallback attempts. The server-level `llm.request_timeout` setting does not extend this routing-specific limit. ## External routing Add a top-level `routing:` block to delegate selection to an external service: ```yaml routing: provider: external base_url: https://gateway.example.com/ai-gateway/routing/v1 router_name: task_v0 model_prefix: databricks- api_key: ${ROUTING_API_KEY} ``` | Field | Required | Description | | -------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------- | | `provider` | Yes | Must be `external`. Any other value, or an omitted block, falls back to the built-in judge when `llm:` is configured. | | `base_url` | Yes | Routing service base URL. The server appends `/routes:select`. | | `router_name` | Yes | Routing strategy, such as `task_v0`. Sent as `route_selector.router_name`. | | `model_prefix` | No | Prefix or list of prefixes removed from catalog model ids before the request and restored on the response, such as `databricks-`. | | `api_key` | No | Static bearer token. `${ENV}` references are expanded. Takes precedence over `profile`. | | `profile` | No | Databricks CLI profile. The server resolves a fresh OAuth token for every request. | If `base_url` or `router_name` is missing, the provider is skipped and routing stays off rather than preventing the server from starting. ## Authentication precedence The first configured option wins: 1. `api_key`: sends `Authorization: Bearer ` after expanding `${ENV}`. 2. `profile`: uses the Databricks SDK to resolve a fresh token for every call, so long-running servers do not retain an expired token. 3. Neither: sends requests without authentication. The request and response contract is documented in the [external routing API reference](https://omnigent.ai/docs/reference/routing-api). --- ## https://omnigent.ai/docs/reference/routing-api # External routing API An Omnigent server can delegate Smart Routing decisions to an external service. See [Smart Routing](https://omnigent.ai/docs/build/routing) for the workflow and [Smart Routing configuration](https://omnigent.ai/docs/reference/configuration/routing) for the server settings. The server calls one endpoint: ``` POST /routes:select Content-Type: application/json ``` The request and response bodies follow the `omnigent.api.routing.v1` schema (proto3, serialized as JSON with snake_case field names). This schema is versioned independently of any gateway. ## Request: `SelectRouteRequest` | Field | Type | Description | | ----------------- | ---------------- | ----------------------------------------------------------------------------------------------- | | `route_options` | `RouteOption[]` | Candidate destinations. One entry is sent for each model and harness pair. | | `task` | `Task` | Work to route. Its `prompt` contains the user's message, truncated to 4,000 characters. | | `route_selector` | `RouteSelector` | Routing strategy. A gateway rejects a request that omits it. | | `session_history` | `SessionHistory` | Prior turns when available. Routers can use them to maintain consistency across a conversation. | ### `RouteOption` | Field | Type | Description | | --------- | -------- | ------------------------------------------------------------------------------------------------- | | `model` | `string` | Model id, such as `gpt-5-5`. | | `harness` | `string` | Harness for the model. It can be omitted for native harnesses and is required for meta-harnesses. | ### `RouteSelector` | Field | Type | Description | | ------------- | -------- | ---------------------------------------------------------------------------- | | `router_name` | `string` | Strategy from the server's `routing.router_name` setting. | | `config` | `Struct` | Optional router-specific configuration interpreted by the selected strategy. | Example: ```json { "route_options": [ { "model": "claude-opus-4-8", "harness": "claude-sdk" }, { "model": "gpt-5-5", "harness": "codex" }, { "model": "gpt-5-4-mini", "harness": "pi" } ], "task": { "prompt": "Refactor the auth module and add tests" }, "route_selector": { "router_name": "task_v0" } } ``` ## Response: `SelectRouteResponse` | Field | Type | Description | | ----------------- | ------------------ | -------------------------------------------------------- | | `route_selection` | `RouteSelection[]` | Routing decisions. The server uses the first entry. | | `rationale` | `string` | Human-readable explanation for the selected destination. | ### `RouteSelection` | Field | Type | Description | | -------------- | ------------- | --------------------------------------------------------------- | | `route_option` | `RouteOption` | Selected model and harness. | | `params` | `Struct` | Optional router-specific parameters returned with the decision. | Example: ```json { "route_selection": [ { "route_option": { "model": "claude-opus-4-8", "harness": "claude-sdk" } } ], "rationale": "Multi-file refactor with tests: favor the most capable model." } ``` The server restores any configured `model_prefix`, then matches the selected model and harness to an offered `route_options` entry. A selection that was not offered is rejected. HTTP errors, malformed responses, and empty selections surface an error and fall back to the session's default harness. --- ## https://omnigent.ai/faq # FAQ ## What is Omnigent? Omnigent is a framework that runs AI agents behind one interface. It wraps Claude Code, Codex, and Pi, plus custom agents written in YAML, and gives them a shared server and UI, with configurable sandboxing and policies. ## Which models can I use? Bring your own API key, Claude or ChatGPT plan, OpenAI- or Anthropic-compatible gateway such as OpenRouter, LiteLLM, Ollama, Azure, or vLLM, or a Databricks workspace. ## How do I run my own agent? Write a YAML file with a prompt and harness, then run `omni run` on it. See the custom agent guide. ## Is it safe to let an agent run on my machine? Omnigent can run agents in a local OS sandbox or a remote [cloud sandbox](https://omnigent.ai/docs/deploy/cloud-sandbox-host). On Linux and macOS, the [OS sandbox](https://omnigent.ai/docs/omnibox) restricts filesystem and network access and brokers credentials without exposing them to the agent. Policies can also pause, block, or cap what an agent does. ## Do I need Databricks? No. Omnigent is open source and runs on your own machine and models. Databricks is one supported model provider. ## Is it ready for production? Teams use Omnigent for real work today. It also moves quickly: releases ship often and can include breaking changes, which recent release notes call out. Pin a version and read the release notes before you upgrade. --- ## https://omnigent.ai/blog/install-custom-agents-from-github # Install a custom agent once from GitHub and use it on every host _Published 2026-10-22 · Agents_ If you keep custom agents in a Git repo and run Omnigent on more than one machine, you can now install an agent once with `omnigent agent add` and pick it from the new-session picker on every host you use. Until now a custom agent only became available by running a session with its spec file, and it belonged to that session. Installed agents are different: they are stored on the server under your account, so they sit beside the built-in agents on every host, with no server restart. Re-adding an agent with the same name replaces your copy in place, and sessions already using it pick up the new version on their next turn (terminal sessions when they relaunch). Workers, skills, guardrails, and supporting files in the agent directory all come along. Point the command at a local agent directory, a YAML file, a `.tar.gz` bundle, or a GitHub repo as `owner/repo`. For a repo, `--list` previews the agents it contains, `@name` installs just one, and a `/tree/` URL pins a branch or tag. GitHub installs ask you to confirm first, since the repo's config runs in your sessions; `--yes` skips the prompt. ```bash # List the agents in your GitHub repo (acme/agents is an example) omnigent agent add acme/agents --list # Install one agent from that repo by its name omnigent agent add acme/agents@orion # Install from a branch or tag of your repo omnigent agent add https://github.com/acme/agents/tree/main/orion # Install from a local agent directory you created omnigent agent add ./orion # List the agents you have installed omnigent agent list # Remove an installed agent omnigent agent remove orion ``` [Video: Installing, listing, and updating a reusable custom agent with the Omnigent CLI](https://omnigent.ai/images/releases/0.18.0/custom-agent-cli.mp4) In the web UI, open the agent picker's custom agents, choose Create custom agent, then Import bundle and pick a `.tar.gz` of an agent directory. The agent installs and is selected for the new session. See the [Custom Agents guide](https://omnigent.ai/docs/use/custom-agents) for how to write the agent itself. [Video: Importing a custom-agent bundle and selecting the installed agent in the new-session picker](https://omnigent.ai/images/releases/0.18.0/custom-agent-import.mp4) --- **Enjoying Omnigent?** If this is useful to you, [give us a star on GitHub ⭐](https://github.com/omnigent-ai/omnigent). Come say hi on [Discord](https://discord.gg/omnigent), or [check the latest release](https://omnigent.ai/releases). --- ## https://omnigent.ai/blog/github-pr-review-in-session # Read your agent's pull or merge request in the session _Published 2026-10-20 · Web UI_ Your agent says it opened a PR and CI passed. To check that, you switch to GitHub, GitLab, or Azure DevOps, read the diff and checks, then return to the conversation. Omnigent's **Pull Requests** panel puts the request description, CI results, comments, and file diffs beside the conversation. You can compare the agent's explanation with the changes before asking it to continue. [Video: Open Pull Requests from a session, inspect Summary and CI checks, then view Changes and select files](https://omnigent.ai/images/blog/github-pr-review-in-session-demo.mp4) _Opening the Pull Requests panel from a session: Summary, CI checks, then Changes and file navigation._ Start with a coding-agent session whose workspace is a git checkout on a supported provider and whose files you can browse. The execution host needs provider access: GitHub uses the GitHub CLI (`gh`); GitLab uses `glab`; Azure DevOps uses Azure CLI sign-in (`az login`) or `AZURE_DEVOPS_EXT_PAT`. For GitHub in a managed sandbox, have the session owner [connect GitHub](https://omnigent.ai/docs/deploy/github-connect#how-it-works) in Omnigent. 1. Open **Pull Requests** in the [workspace rail](https://omnigent.ai/docs/interact/web-ui#github), or click the PR indicator in the composer's workspace bar when one is available. 2. Read **Summary** for the request description and CI checks. Open **Comments** for conversation comments. 3. Switch to **Changes** and use the folder tree to jump between file diffs. If the session has [multiple associated requests](https://omnigent.ai/docs/interact/web-ui#tracked-pull-requests), use the request selector to choose one. PR information refreshes when an agent turn ends. While the panel is open and the app is in the foreground, it also polls for PR and CI updates rather than streaming them in real time. For branch-based lookup, polling normally stops once checks settle or the PR closes or merges; tracked PRs continue polling while the panel is focused. For request tracking and selection, see the [Web UI guide](https://omnigent.ai/docs/interact/web-ui#tracked-pull-requests). The panel shows whether the request is open, closed, or merged. It is read-only: it does not post comments, submit approvals, or merge requests. --- ## https://omnigent.ai/blog/declarative-agent-configuration # Moving agent orchestration from code to configuration _Published 2026-09-24 · Multi-agent_ Agent orchestration systems like [Gas Town](https://steve-yegge.medium.com/welcome-to-gas-town-4f25ee16dd04), [Agent Orchestrator](https://github.com/Untrivial-ai/agent-orchestrator), and [Overstory](https://github.com/jayminwest/overstory) promise coordinated teams that can tackle complex projects with less developer supervision. Many developers I talk to have written their own custom agent orchestration system. I'm no exception: I've written several personal orchestration frameworks that ran different agent harnesses in tmux windows or web terminals, handled messages between agents, and organized work with tickets or kanban boards. I'm not using these custom systems anymore. Even with agents helping to write the code, handling session lifecycles, isolation, message delivery, and runtime adapters is a significant undertaking. Composing these systems inevitably turns into a complex plumbing project. I still think orchestrators and specialist agents can be useful, but building their infrastructure from scratch has just resulted in a list of abandoned projects. Omnigent solves this problem: you can design your multi-agent system and let Omnigent handle the plumbing. ## Agent orchestration as declarative configuration Designing a multi-agent system means deciding which roles exist, how they hand work to one another, what each role can do, and when a person should intervene. Implementing those decisions requires another layer of work: managing sessions, delivering messages and results, connecting tools, and enforcing permissions. Omnigent lets you express the system in declarative YAML. Agents invoke subagents as tools, while Omnigent manages their sessions and communication. Policies and sandboxes define what each role can access. Omnigent supplies the runtime infrastructure that lets agents running in different harnesses work together as one system. | You specify | Omnigent handles | | ---------------------------------------------------- | -------------------------------------------------------------------------------- | | Which roles exist and which harness each uses | Launching each role in the selected harness and managing its session | | What agents hand off to one another | Dispatching work and delivering messages and results | | What each role is allowed to do | Evaluating tool policies and enforcing sandbox boundaries | | How the workflow proceeds and where people intervene | Maintaining inspectable session state and supporting interaction with each agent | ## How to define your own multi-agent system in Omnigent An Omnigent agent can be defined in a single YAML file or in a directory containing separate configurations for its subagents, skills, and tools. The configuration specifies each agent's instructions, harness, model, tool access, policies, and sandbox. The [Custom Agents guide](https://omnigent.ai/docs/use/custom-agents) covers the complete configuration format. Here, we'll focus on the parts needed to compose a small research team. People make all kinds of claims online, and the available evidence is often incomplete or taken out of context. An agent research workflow like the one we describe below could investigate whether a new developer tool actually improves performance, whether an industry practice delivers its claimed benefits, or whether a product's marketing holds up. To demonstrate the workflow, we'll give it a question about coffee co-fermentation, the practice of adding fruit or other ingredients during coffee fermentation. The agent system has four roles: - an orchestrator that delegates and sequences the work; - a claim mapper that turns a broad question into discrete claims; - a claim auditor that researches and evaluates each claim; and - a brief writer that presents the results. We'll walk through some of the key parts here. You can [see the full configuration](https://gist.github.com/djliden/bcdbac7775fbf55437d787d72a7972f4) in the accompanying Gist. ### Set up the agent directory A custom agent configuration can live anywhere on disk. Keep it within a project when it evolves alongside that project, or in a stable personal directory when you want to reuse it. We'll use the directory-based approach for this workflow. The root `config.yaml` defines the orchestrator, while the `agents` directory contains a configuration for each specialist: ```text agents/claim-audit/ ├── config.yaml └── agents/ ├── claim_mapper/config.yaml ├── claim_auditor/config.yaml └── brief_writer/config.yaml ``` An agent directory can also include `skills` and `tools` directories when the team needs custom instructions or tools. ### Configure the orchestrator We'll start with the root orchestrator configuration (the top-level `config.yaml` in the directory tree above), which defines the primary agent, the tools it can use, and its sandbox environment. In this example, the orchestrator uses the Claude SDK with Claude Opus 5. ```yaml # config.yaml spec_version: 1 name: claim_audit_coordinator executor: type: omnigent model: claude-opus-5 config: { harness: claude-sdk } prompt: | You coordinate a four-role claim-audit team. You own phase order, the selected-claim list, and the decision record. You do not search the web, audit evidence, or write the final brief yourself. First dispatch the claim mapper. Select at most four auditable claims, then send each claim to one persistent auditor conversation. Do not dispatch the writer until every selected claim has been audited. # Prompt truncated for the article. The full version defines repair limits, # evidence rules, artifact handoffs, and stop conditions. tools: agents: [claim_mapper, claim_auditor, brief_writer] os_env: type: caller_process sandbox: type: darwin_seatbelt read_paths: ["."] write_paths: [] allow_network: false # Additional coordinator policies omitted; see the full configuration. ``` The orchestrator can read files from its working directory (`read_paths: ["."]`), but it cannot write files or access the network (`write_paths: []`, `allow_network: false`). Its subagents are listed under `tools.agents`, which gives the orchestrator access to the tools it needs to create sessions with them and send them work. ### Define the claim auditor subagent The claim auditor researches and evaluates one claim at a time. To let it research each claim, we give it Omnigent's built-in [`web_search` tool](https://omnigent.ai/docs/build/tools#websearch) and use Nimble as the search provider. This role uses Pi with GPT-5.6 Terra, demonstrating that a subagent can use a different harness and model from its orchestrator. ```yaml # agents/claim_auditor/config.yaml spec_version: 1 name: claim_auditor executor: type: omnigent model: gpt-5.6-terra config: { harness: pi } prompt: | You are the claim auditor in a typed evidence pipeline. You receive one normalized claim plus its mapped observations. Audit that claim without replacing it with an easier or more interesting one. # Prompt truncated for the article. The full version includes the evaluation # rubric, evidence rules, source limits, and output format. tools: builtins: - name: web_search search_provider: nimble api_key: ${NIMBLE_API_KEY} max_results: 3 search_depth: deep # Policy excerpt abridged. The full configuration includes additional # lifecycle allowances and usage limits. guardrails: policies: auditor_tool_allowlist: type: function on: [tool_call] function: path: omnigent.policies.builtins.cel.cel_policy arguments: expression: >- event.type != "tool_call" ? {"result": "ALLOW"} : event.data.name == "web_search" ? {"result": "ALLOW"} : {"result": "DENY"} ``` Nimble requires an API key and its searches may be billable, so the key must be available in the environment where the agent runs. The guardrail uses Omnigent's [CEL policy](https://omnigent.ai/docs/policies/builtin) to permit `web_search` and deny attempts to use any other tool, such as a Bash shell. ### Define the claim mapper and brief writer subagents The claim mapper also uses web search, but for a different purpose. It surveys up to eight sources, identifies no more than four auditable claims, and records who made each claim without evaluating it. Its guardrails limit it to `web_search` and prevent it from modifying files or publishing anything. The brief writer has no web access. Its role is to turn the auditor's independent verification of each claim, along with the sources and context collected by the mapper, into a brief of fewer than 700 words and a matching scorecard. Its sandbox allows it to read the workflow artifacts and write only to the final output directory. We won't cover these two configurations in detail here; both are available in the [full configuration](https://gist.github.com/djliden/bcdbac7775fbf55437d787d72a7972f4). ### Run and register the agent Once the custom agent system is configured, point `omnigent run` at the agent directory to start a session: ```bash omnigent run agents/claim-audit/ ``` `omnigent run` uploads the bundle to the Omnigent server and starts a session. To make the team available as a reusable template independent of that session, register it when starting the server: ```bash omnigent server stop omnigent server --background --agent agents/claim-audit/ ``` You can also ask an agent in Omnigent to create, register, and open a session with a custom team for you. ## Example: A four-agent research workflow ### The prompt We start with a broad question prompted by a specific marketing claim. Here is a condensed version of the prompt: > Royal Coffee described a lemon-lime co-fermented Gesha as “electrified” and “obviously altered” by the addition of citrus juice. What is coffee co-fermentation? How do people say it affects flavor, and what evidence is there that it actually does? Look at scientific research, professional and industry observations, and community experience. Identify the main claims, especially where they conflict, and tell me how well each one stands up. Give me a concise one-page brief and a simple scorecard. The orchestrator sends the question to the claim mapper. ![A live Omnigent claim-audit session showing the final scorecard alongside the coordinator's agent graph.](https://omnigent.ai/images/blog/declarative-agent-configuration/agent-graph-session.png) _A live claim-audit run in Omnigent. The agent graph shows how the coordinator delegates work while the session keeps the resulting scorecard visible._ ### Map and audit the claims The mapper examines five sources to identify claims and who made them, without judging whether they are true. It returns four claims: - Citrus juice can give one washed Gesha a distinctive sensory profile. - Fruit may affect flavor by changing fermentation rather than transferring flavor directly. - Controlled microbial inoculation can improve sensory quality. - Coffee professionals disagree over whether additive fermentation is processing or adulteration. At this stage, a source documents where a claim appeared but it does not establish that the claim is true. The orchestrator sends each claim to the auditor in sequence. The auditor researches it independently, records evidence for and against it, identifies the evidence's limitations, and determines the narrowest conclusion it can support. | Claim | What the auditor found | Conclusion | | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- | | Citrus profile | The seller described one lot as dramatically altered; there was no blinded tasting or untreated control. | `provisionally-supported` | | Microbial mechanism | A coffee professional proposed that fruit changes the fermentation environment, but no comparative study tested that mechanism against direct flavor transfer. | `untestable` | | Sensory quality | One microbial-inoculation study reported an improvement, but it examined a different intervention and its full methods were unavailable to the audit. | `provisionally-supported` | | Classification dispute | Professional and community sources documented disagreement, but not its prevalence or an official competition rule. | `provisionally-supported` | During the run, the auditor process terminated while handling one claim. Omnigent surfaced the failure, and the orchestrator retried the claim in a new session and completed the workflow. We did not have to implement the failed-session or replacement-session mechanics. Once all four claims have been audited, the orchestrator sends the results to the brief writer along with the sources and context collected during mapping. The writer cannot conduct additional research: it has no web tools, its policy permits only local file operations, and its sandbox blocks network access. It returns a [one-page brief](https://gist.github.com/djliden/bcdbac7775fbf55437d787d72a7972f4#file-example-output-brief-md) and a scorecard showing what the available evidence can and cannot establish. Overall, the evidence found and assessed was weak: definitions remain unsettled, direct studies are scarce, and the available record does not support a definitive conclusion about co-fermentation's effects. ## Composing multi-agent systems with Omnigent This claim-audit team demonstrates how Omnigent enables you to define roles, workflows, tools, and policies without having to worry about the runtime. We defined this multi-agent research system without having to implement: - the mechanics of how subagents present their results to the orchestrator; - how the orchestrator dispatches subagents; or - how the guardrails we declared are evaluated and enforced. To try it yourself, start a new Omnigent session and describe the custom agent or agent system you want. An agent can write the configuration using your chosen harnesses, models, tools, skills, MCP servers, policies, and sandboxes, then open a session with it. See the [Custom Agents guide](https://omnigent.ai/docs/use/custom-agents) for more detail, or adapt the [full claim-audit configuration](https://gist.github.com/djliden/bcdbac7775fbf55437d787d72a7972f4). --- **Enjoying Omnigent?** If this is useful to you, [give us a star on GitHub ⭐](https://github.com/omnigent-ai/omnigent). Come say hi on [Discord](https://discord.gg/omnigent), or [check the latest release](https://omnigent.ai/releases). --- ## https://omnigent.ai/blog/stop-surprise-llm-bills # Stop Surprise LLM Bills With Production-Ready Cost Controls _Published 2026-09-03 · Cost Optimization_ > **TL;DR**: Omnigent controls LLM costs at four points: smart routing keeps trivial tasks off expensive models, progressive budgets warn before they block, custom pricing tracks self-hosted models, and the usage page shows where tokens go. Roll it out in phases: visibility first, then routing, then budgets. LLM bills blow through monthly budgets when one debugging session or a runaway multi-agent workflow spirals. LLM costs scale with token consumption, not with how much software you ship. Every character costs money. Long conversations accumulate context, multi-agent workflows multiply that, and a hard cap that fires mid-task interrupts real work. Most teams see the bill only after the money is spent. Omnigent addresses this at four points: 1. **Prevention**: Smart routing keeps trivial tasks off expensive models. 2. **Control**: Progressive budgets warn before they block, then downgrade instead of stopping. 3. **Visibility**: The usage page tracks spend with per-harness and per-model breakdowns. 4. **Safeguards**: Thrashing detection, scheduled-task limits, and terminal approvals. ## Smart task routing The cheapest token is the one you never spend. Omnigent's smart routing policy classifies each user message as TRIVIAL or COMPLEX and denies trivial messages from reaching expensive models. ### How it works When a session tries to use an expensive model (Opus, Fable, GPT-5), Omnigent classifies the user's message with a lightweight LLM: ```yaml policies: trivial_gate: type: function function: path: omnigent.policies.builtins.routing.deny_trivial_to_expensive_model arguments: expensive_models: ["opus", "fable", "gpt-5"] ``` The two categories: - **TRIVIAL** (blocked on expensive models): factual lookups, greetings and status checks, one-line code changes, short Q&A - **COMPLEX** (allowed on any model): multi-step reasoning, code analysis, open-ended research, architecture decisions An example flow: ``` User: "Fix the authentication bug in login.py" → Classifier: COMPLEX → Opus request: ALLOWED User: "What's the current time?" → Classifier: TRIVIAL → Opus request: DENIED → User switches to Haiku ``` Trivial messages get routed to a cheaper tier, so complex work still runs on Opus or Fable while short lookups do not. See the [routing documentation](https://omnigent.ai/docs/build/routing) for the full policy reference. ## Progressive budget policies Routing cuts expensive model calls, but real workflows still burn tokens. Omnigent's [contextual policy system](https://omnigent.ai/blog/omnigent-policies) adds budgets that warn before they block. ### Session cost budget ```yaml policies: cost_budget: type: function function: path: omnigent.policies.builtins.cost.cost_budget arguments: max_cost_usd: 5.0 ask_thresholds_usd: [1.0, 2.5] expensive_models: ["opus", "fable", "gpt-5"] ``` Soft thresholds warn at $1 and $2.50. Each checkpoint prompts at most once: after you approve it, later turns stay quiet until the next threshold. The $5 hard limit is a downgrade gate, not a stop. It blocks expensive models but lets work continue on cheaper tiers (Sonnet, Haiku). ### Per-user daily cost budget Per-user daily budgets track cumulative spend across all of a developer's sessions for the current UTC day: ```yaml policies: user_daily_cost_budget: arguments: max_cost_usd: 10.0 ask_thresholds_usd: [3.0, 7.0] expensive_models: ["opus", "fable"] ``` A 5-developer startup setting `max_cost_usd: 10.0` caps monthly spend at ~$1,500 (5 × 30 × $10). See the [cost budget policy reference](https://omnigent.ai/docs/policies/builtin#cost_budget) for all arguments. ## Custom pricing for self-hosted models Teams running self-hosted models on Ollama, vLLM, or a custom inference gateway used to see "unpriced spend" warnings, which disabled budget enforcement because Omnigent had no price to meter against. Declare the price yourself: ```yaml providers: ollama-local: kind: local openai: base_url: "http://localhost:11434/v1" pricing: input_per_million: 0.0 output_per_million: 0.0 vllm-gateway: kind: gateway openai: base_url: "https://inference.company.internal/v1" pricing: input_per_million: 0.25 output_per_million: 1.0 ``` The warnings go away and budgets meter correctly. This also covers cost attribution for self-hosted models, zero-cost local development, and internal chargeback. ## Model selection ### Sticky model choices The composer remembers your model choice per harness. Switch from Opus to Sonnet once, and Sonnet becomes the default for future sessions on that harness until you change it. The output-token prices differ by 5x: Opus is $15 per million, Sonnet is $3 per million. A team that moves routine coding from Opus to Sonnet pays a fifth as much for that work, and can still pick Opus per session when a task needs it. Select the model from the dropdown in the New Chat composer. The choice sticks per harness (Claude Code, Codex, and so on). ## Cost visibility ### Usage page The usage page (`/usage`) breaks down LLM spend: 1. **Daily cost timeline**: bar chart, with zero-spend days filled in 2. **Cost by harness**: which execution environments spend the budget 3. **Cost by model**: which tiers drive spend (Opus vs Sonnet vs Haiku) 4. **Sortable session table**: session (with its model as a badge), harness, cost, last active ### Multi-model session display Sessions that switch models show every model used: ``` Session: "Debug authentication flow" Model: claude-opus-4 +2 ``` Hover `+2` to see the other models, sorted by cost. This answers "Why did this cost $3 when I thought I was on Sonnet?" ### Sub-agent harness visibility Multi-harness workflows show which execution environments ran. The primary harness displays with a `+N` badge for the rest; hover it to see the others: ``` Harness: claude-code-native +1 → Hover: "antigravity" ``` This matters when you run more than one provider (Anthropic, OpenAI, Gemini). ## Safeguards ### Native terminal cost popups Approval popups render in the terminal via `tmux display-popup`: ``` ┌───────────────────────────────────────────────┐ │ ⚠️ Policy approval required │ │ │ │ Session cost $1.05 passed the $1.00 │ │ warning threshold (limit $5.00). │ │ │ │ [y] approve [n] decline │ └───────────────────────────────────────────────┘ ``` The terminal popup and the web UI resolve the same approval request; whichever you answer first wins. ### Scheduled task cost budgets Unattended tasks attach a cost budget automatically: ```json POST /v1/scheduled-tasks { "title": "Daily code review", "schedule": "0 9 * * *", "max_cost_usd": 2.0 } ``` This stops a single cron run from spending $50 on an unexpectedly large diff. ### Detect thrashing policy The thrashing policy catches agents stuck in retry loops: - Consecutive errors (default: 5 failures) - Error rate (default: 80% over 10 results) When it fires, it ends the session before repeated retries run up the bill. See the [detect thrashing policy reference](https://omnigent.ai/docs/policies/builtin#detect_thrashing). ### Archived session retention Auto-cleanup of archived sessions runs on a schedule you set (7, 30, 60, or 90 days) under Settings → Archived sessions, which cuts storage and clears the session list. For example, a 50-person team archiving about 200 sessions a month holds steady near 400 sessions at 60-day retention, rather than accumulating 2,400 over a year. ## How the layers fit together Omnigent handles LLM cost at four layers: **Prevention:** - Smart routing keeps trivial tasks off expensive models. - Sticky model selection makes a cheaper default stick per harness. - Neither changes how you work. **Control:** - Progressive budgets go soft warning, then downgrade gate, then hard limit. - Budgets scope to a session or to a user's whole day. - Custom pricing lets budgets meter self-hosted models. - Scheduled-task limits cap unattended runs. **Visibility:** - The usage page shows daily timelines and per-harness, per-model breakdowns. - Sessions and sub-agents report every model and harness they touched. - You see spend as it happens, not at month end. **Safeguards:** - Terminal popups approve or decline without leaving the terminal. - Thrashing detection ends retry loops before they run up the bill. - Archived-session retention cleans up on a schedule you set. What is distinct here: budgets warn and downgrade rather than hard-stop, routing and budgets and visibility work together instead of in isolation, self-hosted models get real prices, and every token is attributed to a model and harness. ## Rolling it out Deploy in four phases, each building on the last. Turn on visibility first so later thresholds are grounded in real spend, then prevention, then hard control, then ongoing cleanup. ### Phase 1: Observe (Week 1) Turn on the usage page and switch the team to a cheaper default model. Spend becomes visible and the bill drops right away, with no policy config yet. ```bash export OMNIGENT_FEATURES=usage_page ``` In the New Chat composer, pick Sonnet or Haiku instead of Opus from the Model dropdown. The choice sticks per harness. ### Phase 2: Prevent (Week 2) Add smart routing to keep trivial requests off expensive models, and set custom pricing so budgets can meter self-hosted spend. ```yaml policies: trivial_gate: type: function function: path: omnigent.policies.builtins.routing.deny_trivial_to_expensive_model arguments: expensive_models: ["opus", "fable"] # If using self-hosted models: providers: ollama-local: kind: local openai: pricing: input_per_million: 0.0 output_per_million: 0.0 ``` ### Phase 3: Control (Month 2) Add progressive budgets, tuning the thresholds against what Phase 1 showed you. Warnings fire before any limit, and the hard cap downgrades rather than blocks. ```yaml policies: user_daily_cost_budget: arguments: max_cost_usd: 10.0 ask_thresholds_usd: [3.0, 7.0] expensive_models: ["opus", "fable"] ``` ### Phase 4: Optimize (Month 3+) Turn on thrashing detection, set archived-session retention (30 or 60 days), and revisit thresholds monthly as usage shifts. ### Budgets by team size Scale the daily budget with headcount. These are starting points to tune, not hard rules: ```yaml # Small teams (5-15 developers) user_daily_cost_budget: max_cost_usd: 5.0 # ~$150/month per dev ask_thresholds_usd: [2.0] # Mid-size (15-50) user_daily_cost_budget: max_cost_usd: 10.0 # ~$300/month per dev ask_thresholds_usd: [3.0, 7.0] # Enterprise (50+) user_daily_cost_budget: max_cost_usd: 25.0 # Higher limits for experienced engineers ask_thresholds_usd: [10.0, 20.0] scheduled_task_cost_budget: max_cost_usd: 2.0 # Strict limits for automation ``` --- Coding agents are now part of the daily workflow, and their cost needs the same attention as any other infrastructure. These features give teams the visibility and control to scale that usage without the surprise bill at the end of the month. Start with the [documentation](https://omnigent.ai/docs/policies/builtin), set up a [cost budget policy](https://omnigent.ai/docs/policies/builtin#cost_budget), and turn on [smart routing](https://omnigent.ai/docs/build/routing). --- **Learn more:** _Documentation:_ - [Builtin policies](https://omnigent.ai/docs/policies/builtin): reference for cost budget, routing, and thrashing detection - [Smart routing](https://omnigent.ai/docs/build/routing): configure built-in or external routing to match tasks to models - [Contextual policies overview](https://omnigent.ai/docs/policies/overview): how policies intercept and control agent actions _Related posts:_ - [Allow, ask, deny: contextual policies in Omnigent](https://omnigent.ai/blog/omnigent-policies): how the policy system works - [Organize your sessions into projects](https://omnigent.ai/blog/first-class-projects-with-defaults): keep related sessions organized with default settings --- ## https://omnigent.ai/blog/nimble-web-research-extract-builtins # Give your agents built-in structured web research and extraction _Published 2026-08-28 · Tools_ If you build agents that need real information from the live web, you can now turn on two ready-made tools instead of wiring up your own scraping and search code. Add `nimble_research` and `nimble_extract` to an agent spec by name and your agent can run a cited research task or pull structured data straight off a page. Getting good web data into an agent usually means gluing together a search API, a scraper, and your own parsing, then keeping all of it working. `nimble_research` hands a task to a Nimble Web Search Agent. The agent plans the search itself and browses in a real browser, so JavaScript-rendered and interactive pages still resolve. It cross-checks what it finds across sources and returns an answer with per-claim citations and a confidence signal for each one. Pass an `output_schema` to get typed JSON instead of prose. Pass `input_data` and it enriches the records you give it instead of answering a question, which is what you want when the job is a few thousand records rather than one lookup. Each run also stores its sources and the retrieval paths that worked, so an agent that runs this kind of task repeatedly builds on earlier runs instead of starting cold. `nimble_extract` runs one of your Nimble extract templates against the live web and returns the parsed result as structured JSON in a single call. Both resolve inside the runner, so they work with any agent you run in Omnigent, not just one model family. [Video: A Claude-SDK agent picks nimble_research to find five in-stock single-origin coffees on Intelligentsia's live storefront, runs it over several minutes, then returns a structured table of name, price, bag size, processing, and product URL, with per-claim citations, a high-confidence signal, and caveats for the listings it excluded as out of stock](https://omnigent.ai/images/blog/nimble-web-research-extract-builtins-demo.mp4) _The agent chooses `nimble_research`, reads Intelligentsia's live product pages, and returns a cited, structured result with its confidence and the listings it ruled out._ Enable them under `tools.builtins` in your agent spec and pass your Nimble API key. `nimble_research` needs the optional `nimble` extra (`pip install 'omnigent[nimble]'`); `nimble_extract` names one of your account's extract templates. ```yaml tools: builtins: - name: nimble_research api_key: ${NIMBLE_API_KEY} - name: nimble_extract api_key: ${NIMBLE_API_KEY} template: my_template_name # one of your account's extract templates ``` For a quick lookup rather than a full research run, Nimble is also a [`web_search`](https://omnigent.ai/docs/build/tools#websearch) provider, and has been since that tool shipped. Set `search_provider: nimble` and results come back in seconds instead of the minutes a research run can take, with no extra to install. See the [built-in tools guide](https://omnigent.ai/docs/build/tools#built-in-tools) for how builtins are configured, and [Nimble's Omnigent connector docs](https://docs.nimbleway.com/integrations/connectors/omnigent) for the full config reference on all three capabilities, including where to get an API key. --- **Enjoying Omnigent?** If this is useful to you, [give us a star on GitHub ⭐](https://github.com/omnigent-ai/omnigent). Come say hi on [Discord](https://discord.gg/omnigent), or [check the latest release](https://omnigent.ai/releases). --- ## https://omnigent.ai/blog/omnigent-policies # Allow, ask, deny: contextual policies in Omnigent _Published 2026-08-13 · Policies_ If you've worked with coding agents before, you've seen them pause and ask before editing files or reading the contents of certain directories. In Omnigent, these events are governed by _policies_. A [policy](https://omnigent.ai/docs/policies/overview) is a rule that runs at specified points in a session, such as when the user sends a request to the agent, when the agent replies, or when the agent attempts to use a tool, and returns one of three decisions. - `ALLOW`: let the action proceed - `ASK`: pause and ask the user to approve or reject it - `DENY`: block the action Suppose, for example, an agent is about to use a web fetch tool on a certain site. A policy might check if that site is on an allowed list of domains before allowing the tool call to proceed. Omnigent policies are _contextual_. Each policy maintains its own state throughout a session and can make decisions based on prior events and information from the whole session. For instance, a policy might block certain actions if personally identifiable information (PII) has been introduced to the chat at some point, but permit them otherwise. Or a policy might restrict access to expensive models after a session passes a set budget threshold. The best way to learn how policies work is to try a few of them out. Omnigent has a selection of built-in policies and lets you define your own custom policies. We'll explore both usage patterns. Policies can apply to a single session, to an agent, or across the whole server; see [adding a policy](https://omnigent.ai/docs/policies/overview#adding-a-policy). By the end of this post you should understand what a policy is and how to think about implementing policies in your projects. This post is not comprehensive: consult the documentation for specific guidance on creating your own policies. ## Example 1: Ask Before File and Shell Operations Suppose we want our agent to help with local development, but do not want it running shell commands or editing files without explicit permission. There is a built-in policy we can add to ask the user for approval before allowing the agent to execute any file or shell operations. You can add the policy to your current session from the Omnigent web app or desktop app by selecting the info button (ⓘ) in the top-right corner, clicking `+` next to `policies`, and selecting "Require Approval for File & Shell Operations." ![Omnigent Add Policy dialog listing policies including Require Approval for File & Shell Operations](https://omnigent.ai/images/blog/omnigent-policies/add-policy-dialog.png) _The Add Policy dialog, with "Require Approval for File & Shell Operations" in the list._ You can also simply ask the agent to add the policy to your session—Omnigent supplies coding agents with the tools they need to add policies themselves. Now, whenever the agent attempts a file or shell operation, it will first ask the user for permission. ![Omnigent showing an Approval required panel before a file write, with Approve and Reject buttons](https://omnigent.ai/images/blog/omnigent-policies/file-write-approval.png) _Omnigent pausing for approval before a file write, with Approve and Reject buttons._ ## Example 2: A Custom Policy in Python You may want to implement a policy over actions or session states that are not covered by built-in policies. Maybe you want to limit the use of a specific tool, or block an action once sensitive data has entered the session. With Omnigent, you can define [custom policies](https://omnigent.ai/docs/policies/custom) to meet your specific needs. Fundamentally, a custom policy is a Python function that receives a `PolicyEvent` and returns a `PolicyResponse` (`ALLOW`, `ASK`, or `DENY`). A `PolicyEvent` describes something that just happened or is about to happen, such as a message arriving, the agent about to call a tool, a tool returning a result, or the agent about to reply. In our Python code, we can match on `event["type"]` to pick out the event we care about, and read `event["session_state"]` to see what has already happened this session. The example below gives the agent a budget of free shell calls, then asks for approval on every one after that. ```python from omnigent.policies.schema import PolicyEvent, PolicyResponse FREE_SHELL_CALLS = 3 def budget_shell_calls(event: PolicyEvent) -> PolicyResponse | None: if event.get("type") != "tool_call": return None if (event.get("data") or {}).get("name") != "sys_os_shell": return None used = (event.get("session_state") or {}).get("shell_calls_used", 0) if used >= FREE_SHELL_CALLS: return { "result": "ASK", "reason": f"That's shell call #{used + 1} this session — approve it?", } return { "result": "ALLOW", "state_updates": [ {"key": "shell_calls_used", "action": "increment", "value": 1} ], } ``` This policy is _stateful_. It reads a counter out of `event["session_state"]` and returns a `state_updates` list to bump that counter each time it allows a call. Omnigent persists the counter across the session, so the same `echo` command is allowed the first three times and paused the fourth—nothing changed but the history. See [Contextual Policies in Omnigent](https://www.databricks.com/blog/contextual-policies-omnigent-using-session-state-better-govern-ai-agents) for deeper examples, like a running cost total. You can attach this custom policy directly to your [agent config](https://omnigent.ai/docs/use/custom-agents), the YAML file `omni run` reads: ```yaml policies: budget_shell_calls: type: function handler: myorg_policies.budget_shell_calls ``` To use the policy, save the function in `myorg_policies.py` next to the agent config. Stop the running server if you have one, then start the agent: ```bash omni stop omni run agent.yaml ``` Run this from the directory where both files are located. `omni run` resolves your module against the directory you launch from. ![Omnigent budget shell calls policy pausing for approval on the fourth shell call](https://omnigent.ai/images/blog/omnigent-policies/shell-budget-approval.png) _The budget policy pausing for approval on the fourth shell call._ You can also register it on the server, which makes it selectable from the UI for everyone on that server. Add your module to the `policy_modules:` list in the server config and export a `POLICY_REGISTRY` list from it; the server scans that list at startup. See the [docs](https://omnigent.ai/docs/policies/custom#2-register-on-the-server) for the entry format. ## Example 3: An Inline Policy in CEL Not every policy needs a Python file. For simple, stateless checks you can write the whole policy as a single [Common Expression Language (CEL)](https://cel.dev/overview/cel-overview) expression, right in the agent config. Here's a policy that denies any shell command containing `rm -rf`: ```yaml policies: block_rm_rf: type: function handler: omnigent.policies.builtins.cel.cel_policy factory_params: expression: >- event.type == "tool_call" && event.target == "sys_os_shell" && event.data.arguments.command.contains("rm -rf") ? {"result": "DENY", "reason": "Recursive delete is blocked."} : {"result": "ALLOW"} ``` Only `sys_os_shell` calls get checked: the tool-name test comes first, so calls to other tools—which have no `command` field to read—pass through without erroring. And unlike the Python policy above, a CEL policy is just an expression with nothing to load, so the agent can add one to a live session immediately, no restart needed. ![Omnigent chat where echo succeeds but rm -rf is denied by policy](https://omnigent.ai/images/blog/omnigent-policies/recursive-delete-denied.png) _The custom policy denying an `rm -rf` shell command with "Denied by policy: Recursive delete is blocked."_ ## Conclusion Policies look at distinct events, and optionally at tracked state, and return `ALLOW`, `ASK`, or `DENY`. Omnigent ships with built-in policies for common needs like safety and cost control, and you can write your own for boundaries specific to your tools, data, and risk tolerance. Try it yourself: add one to a live session and watch what happens when it's triggered. Next, think about what actions you don't want your agents to take unsupervised, and make it `ASK` first. --- ## https://omnigent.ai/blog/meta-harness-missing-layer # Meta-Harness: the Missing Layer Above Claude Code, Codex, and Pi _Published 2026-07-30 · Multi-agent_ Different coding agents and agent harnesses have different strengths. Using them together lets you combine those strengths: you can assign different parts of a job to different models and run them in parallel, have one agent check another's work, or give lower-complexity tasks to less expensive or more token-efficient harnesses. But using multiple agent harnesses together isn't always easy (if it's possible at all). You may find yourself writing one-off scripts to let the agents work together, or manually copying context from one agent UI to another. Each agent harness has its own approach to cost controls, sandboxing, permissions, and sharing, so you need to learn how multiple different systems work. You should not have to manually coordinate work across several agent interfaces to combine the strengths of different agent harnesses. Omnigent is a _meta-harness_ that sits above the coding harnesses you already use, such as Claude Code, Codex, Pi, OpenCode, and Hermes Agent, and provides an orchestration, collaboration, and policy layer across all of them. It is driven by declarative configuration and gives you a single interface for working with agents and sharing sessions. In other words, Omnigent lets you use the strengths of each agent harness while handling many of the challenges for you. In the rest of this post, we'll see how [Polly](https://omnigent.ai/quickstart/polly), a multi-agent orchestrator built into Omnigent, handles that coordination on a small coding task. [Video: Opening one of Polly's subagent sessions in Omnigent](https://omnigent.ai/images/blog/meta-harness-missing-layer/polly-subagent.mp4) _Opening one of Polly's subagent sessions in Omnigent._ ## Polly orchestrates a bug fix with Claude Code, Codex, and Pi I wrote a small client to pull a GitHub repository's open issues and group them by label to help with issue triage. On a personal project with only a small handful of issues, it worked great, quickly listing the outstanding issues and top labels. I decided to try it out against the [Omnigent](https://github.com/omnigent-ai/omnigent) repo, though, and it only returned 30 issues. At the time of writing this article, the Omnigent repo had well over 250 open issues. Furthermore, the two labels at the top of the output, `size/XL` and `needs-demo`, are both pull request labels. (You might guess at this point—correctly—that I'm not super familiar with the GitHub API!) ```text $ python -m ghlite.digest omnigent-ai/omnigent Open issues in omnigent-ai/omnigent: 30 Top labels: 10 size/XL 9 needs-demo 9 triaged 7 size/M 7 Bug 5 size/S 4 size/L 3 help wanted 3 P2-medium 2 enhancement ``` Fixing this would require reviewing the client code and getting a better understanding of the GitHub API, then making and reviewing several separate changes. This is a good fit for a multi-agent system: one agent can examine the client while another checks the API, and the resulting fixes and reviews can be planned and assigned in sequence or in parallel as needed. Omnigent includes a built-in agent configuration called Polly for coordinating that work. Polly decomposes goals into tasks and dispatches subagents to complete them. Each task gets its own agent harness and git worktree. The subagent sessions remain visible in Omnigent, so you can open one at any time to see what the subagent is doing, ask questions about its part of the work, or redirect it. The whole session, including the orchestrator and the subagents, is governed by a `blast_radius` [orchestration policy](https://omnigent.ai/docs/policies/builtin#orchestration) that blocks irreversible commands such as force pushes while allowing routine reads, edits, tests, and local git operations to proceed without prompting. ### Multi-agent investigation I handed the client repository to Polly and invoked Polly's `/investigate` skill to find out why my triage client broke when used on a larger repo. It dispatched two subagents on different harnesses, one to read my code and the other to query the GitHub API. ![Polly runs the investigation read-only, across two different harnesses.](https://omnigent.ai/images/blog/meta-harness-missing-layer/polly-parallel-investigation.png) _Polly runs the investigation read-only, across two different harnesses._ The investigation found that the repo actually had 294 open issues, not 30. My client made a single API request and parsed the results, but the GitHub API returns results one page at a time (30 by default) and points to the next page with a `Link: rel="next"`. So the naive implementation read the first page and stopped. This was easy to miss when testing the client on a small repository with only a handful of issues! The issues endpoint also returns pull requests alongside issues unless you filter them out, which is where the `size/XL` and `needs-demo` labels were coming from. The investigation also found that the client had no retry logic and no handling for GitHub's rate limits. These issues didn't show up when testing on a small example repo, but both could break the client on a larger, more active project. ![Polly traced the incorrect count to a single unpaginated request. Its two investigation subagents independently confirmed that the repository had 294 open issues.](https://omnigent.ai/images/blog/meta-harness-missing-layer/polly-investigation-findings.png) _Polly traced the incorrect count to a single unpaginated request. Its two investigation subagents independently confirmed that the repository had 294 open issues._ After the investigation, I instructed Polly to focus on three main fixes: - **Pagination:** follow the `Link: rel="next"` header and fetch every page. - **Filter out pull requests** so the counts and labels reflect issues only. - **Retry and rate-limit handling** for failed requests and GitHub's limits. ### Different agents review each other's work Once I gave it the go-ahead to start coding, Polly loaded its `/fanout` skill to run the series of fixes. It started with the pagination fix, since the other two changes depended on it. Polly handed that change to Claude Code, which wrote the pagination code and its tests, got them passing, and opened a PR. After the pagination change was written, reviewed, and fixed, Polly handed the other two changes out in parallel, one to a Codex subagent and one to a Claude Code subagent, each in its own git worktree to avoid collisions. It stacked those two PRs on the pagination branch rather than on `main`, so each one showed only its own diff. After each change finished, Polly sent its diff to another agent for review. The reviewer and implementer ran on different agent harnesses, so the reviewer was less likely to share the implementer's blind spots. | **Fix** | **Implemented by** | **Reviewed by** | **Review outcome** | | ------------------- | ------------------ | --------------- | ----------------------- | | Pagination | Claude Code | Codex | one issue found → fixed | | Filter out PRs | Codex | Pi | passed | | Retry & rate limits | Claude Code | Codex | one issue found → fixed | I ended up with three PRs, each reviewed by a different agent from the one that implemented it. Two of the three reviews caught issues that needed to be resolved. On the pagination change, the reviewer noticed it could loop forever on a cyclic `next` link, with no guard to stop it. On the resilience change, the reviewer noticed it handled only the numeric form of GitHub's `Retry-After` header and ignored the HTTP-date form, so it could wait less than the server asked. The two changes went back to their implementers, who fixed the issues and added tests. All of this work happened in Omnigent. I did not need to navigate between different harness-specific UIs; I never copied and pasted code or other context; and harness-specific permission and approval systems never interfered with the work. ## Try it yourself Omnigent is a _meta-harness_: a shared orchestration, collaboration, and policy layer that sits above the agent harnesses you already use. One of the easiest ways to try it is with Polly, a built-in multi-harness coding orchestrator that uses the coding harnesses already installed on your system to split up a coding job, run independent tasks in parallel, and send each change to a different agent for review. [Install Omnigent](https://omnigent.ai/quickstart/install) with `curl -fsSL https://omnigent.ai/install.sh | sh`, then run `omni setup` to configure your agent credentials. From your project directory, start Polly with `omni polly`. Give it a bug to investigate or a coding task to split up, then open the subagent sessions and follow their work. --- ## https://omnigent.ai/blog/first-class-projects-with-defaults # Organize your sessions into projects, with optional defaults that pre-fill every new chat _Published 2026-07-27 · Projects_ A project is how you keep related sessions together in Omnigent instead of letting them pile up in one flat list. Now each project can also carry its own default session settings, so if the work inside it tends to run the same way, every new chat can open pre-filled. [Video: Open a project's settings, set a host, working directory, and agent, then start a new session in that project and see the composer pre-filled](https://omnigent.ai/images/releases/0.7.0/projects.mp4) ## Projects organize your sessions A project is a named home for the sessions that belong together: everything tied to one codebase, one investigation, or one recurring task in a single place. In the sidebar you can create a project, rename it, and delete it, then file each session into the project it belongs to, so you can find your way back to a piece of work without scrolling through unrelated chats. That grouping is the point of a project. The sessions inside it are free to differ however you need, and none of them have to share a host, an agent, or any other setting. ## Defaults are an optional convenience Often, though, the sessions in a project do run the same way: the same host, the same working directory, the same agent. When that is true, you can save those as the project's defaults so you are not re-selecting the same options every time you pick up work in a familiar context. Each project can store a default host, working directory, and agent, plus an optional fresh git worktree per session. When you start a chat inside the project, the composer reads those defaults and pre-fills them for you. None of this is required, and nothing is locked in. Leave the defaults unset and a project stays a pure organizing tool, with new sessions falling back to your usual settings. Set them and you still see every field and can change any of them before you send. Any default that no longer fits, like a host that is offline or an agent that is no longer installed, is quietly skipped so you never start from a broken setup. ## Setting a project's defaults If you want them, setting a project's defaults takes a moment. Open the project folder's `⋯` menu in the sidebar and choose Project settings. Pick the host, working directory, and default agent, and turn on Random worktree if you want each new session to branch into its own fresh git worktree. Leave a field unset and new sessions fall back to your usual defaults. From then on, the shortcut to start a new session in that project's folder opens the composer already filled in. See the [Web UI guide](https://omnigent.ai/docs/interact/web-ui#projects) for how projects work in the sidebar. --- **Enjoying Omnigent?** If this is useful to you, [give us a star on GitHub ⭐](https://github.com/omnigent-ai/omnigent). Come say hi on [Discord](https://discord.gg/omnigent), or [check the latest release](https://omnigent.ai/releases). --- ## https://omnigent.ai/blog/any-acp-agent-with-tools-and-governance # Plug any ACP agent into Omnigent with your tools and governance built in _Published 2026-07-10 · Multi-harness_ If you run an agent that speaks the Agent Client Protocol, whether it's Qwen Code, Goose, Grok, Kilo Code, or something you built in-house, you can now point Omnigent straight at it and drive it from the same web UI as every other agent. Before this, a new agent meant a new integration. The generic `acp` harness removes that step: you give Omnigent a launch command and it speaks ACP against whatever that command starts. Omnigent renders the agent's streaming output, reasoning, and tool cards, routes its permission requests through your contextual policies as approval cards, and honors interrupts. It also hands the agent Omnigent's own builtin tools (session, sub-agent, skill, and policy tools) alongside the agent's native ones, so an outside agent runs inside your governance from the first turn. [Video: Registering a custom ACP agent in omnigent setup, then running a turn in the web UI with its tool calls routed through Omnigent's governance](https://omnigent.ai/images/blog/custom-acp-harness-2x.mp4) _A custom ACP agent running in the Omnigent web UI, with its tool calls routed through Omnigent's governance._ Each ACP agent brings its own auth, so log into it through its own CLI first; Omnigent stores no credential. Register an agent with `omnigent setup`, then _configure harnesses_, then **Custom ACP agent**, then _Add_, giving it a name, a launch command, and an optional model. Paste-ready commands include: ```text qwen --acp goose acp grok agent stdio kilo acp ``` Each registered agent gets a stable slug and surfaces as its own harness id, `acp:`, in the web picker and in agent YAML. To launch one straight from the CLI: ```bash omni run --harness acp: ``` See the [harness configuration docs](https://omnigent.ai/docs/build/harnesses/configuration) for the full setup, config format, and the `OMNIGENT_ACP_MCP=0` switch if you want to turn the builtin-tool bridge off. --- **Enjoying Omnigent?** If this is useful to you, [give us a star on GitHub ⭐](https://github.com/omnigent-ai/omnigent). Come say hi on [Discord](https://discord.gg/omnigent), or [download the latest release](https://omnigent.ai/download). --- ## https://omnigent.ai/blog/queue-and-steer-messages-while-agents-work # Line up your next moves while an agent works — steer or reorder them mid-run _Published 2026-07-10 · Web UI_ During a long agent run, you no longer have to wait for each turn to finish before lining up your next move. Type a follow-up while the agent is still working and it joins a queue instead of interrupting what the agent is doing. Rather than waiting in your head or risking landing in the wrong place, your messages sit in a strip above the composer and are sent automatically, one at a time and in order, as soon as the agent is free. [Video: Queue two follow-ups mid-turn in the docked strip, reorder them, then steer one so it lands while the agent is still responding](https://omnigent.ai/images/blog/queue-and-steer-messages-while-agents-work-demo.mp4) Nothing you line up early is locked in. Hover any queued message to edit its wording, reorder it by dragging, delete it, or steer it to send it right away without waiting for the queue. Steering does not stop the agent; it works your message into what it is already doing at the next natural point. This works with any agent you run in Omnigent. This is on by default, with nothing to set up. Just type while the agent is working and press send. For the full list of what you can do with a queued message, see the [Web UI guide](https://omnigent.ai/docs/interact/web-ui#message-queue-and-steering). --- **Enjoying Omnigent?** If this is useful to you, [give us a star on GitHub ⭐](https://github.com/omnigent-ai/omnigent). Come say hi on [Discord](https://discord.gg/omnigent), or [check the latest release](https://omnigent.ai/releases). --- ## https://omnigent.ai/releases/0.19.1 # v0.19.1 _Released 2026-10-21_ · 1 min read · Omnigent maintainers Omnigent 0.19.1 is a security-focused patch release. It contains no new features. It updates Omnigent's dependencies to mitigate the impact of the recent [eMudhra Intermediate CA Compromise](https://emudhra.we-have-a-problem.com/). We recommend upgrading to 0.19.1 so your installation picks up the updated dependencies. Full Changelog: https://github.com/omnigent-ai/omnigent/blob/main/CHANGELOG.md ## What's Next Install or upgrade Omnigent: ```bash uv tool install --python 3.12 omnigent # or: pip install "omnigent" ``` - Star the project and file issues on [GitHub](https://github.com/omnigent-ai/omnigent). - Join the conversation on our [Discord](https://discord.gg/omnigent). - Browse the [docs](https://omnigent.ai/docs) to go deeper. --- ## https://omnigent.ai/releases/0.19.0 # v0.19.0 _Released 2026-10-20_ · 3 min read · Omnigent maintainers Omnigent 0.19.0 is a major release focused on giving your agents safe access to the tools and code they need and making their work easier to steer, from connecting managed MCP tools and private Git repositories to tuning, editing, and reviewing what your agents produce. On servers with an MCP registry, you can now connect managed MCP accounts and route their tools through a server gateway with shared policy checks. Sandboxed agents can work with approved Git repositories over SSH without ever holding the key. You can also set reasoning effort for SDK agents from the web composer, edit custom agents right in Settings, and see much more review context in the Pull Requests panel. ## 1. MCP Registry and Gateway ![Choosing MCP services before launching a session](https://github.com/user-attachments/assets/0480201e-045a-4060-a7dc-64ebee029da9) Wiring MCP tools into every agent by hand gets old fast, especially when the same accounts need to work on your laptop and in a sandbox. If your server has an MCP registry configured, you connect managed MCP accounts once, in Settings, and then choose which of their tools your agents can use. Those tools go through a server gateway whether your agent runs on a local host or in a sandbox. Because every call passes through the gateway, the same policy checks apply wherever the agent runs. _Learn more in the [MCP servers](https://omnigent.ai/docs/build/tools#mcp-servers)._ ## 2. Custom Agent Editing in Settings ![Editing a custom agent's description, harness, model, instructions, and MCP servers in Settings](https://github.com/user-attachments/assets/1d61c4e1-be38-4d71-a391-c605d9c57c90) Changing a custom agent shouldn't mean rebuilding it from scratch. You can now edit your custom agents directly from Settings and adjust their configuration in place. If you manage agents from code, a conditional PATCH API lets you update individual settings without resending the whole configuration. _Learn more in the [Custom Agents](https://omnigent.ai/docs/use/custom-agents)._ ## 3. Reviewers and Comments in the Pull Requests Panel ![Pull Requests panel showing reviewer states, checks, and the separate Comments tab](https://github.com/user-attachments/assets/a67ac11e-fe6e-4750-aab3-1dbf23b4a792) Following a pull request used to mean switching back to GitHub to see who still had to review it and what people had said. The Pull Requests panel now shows pending reviewers and review states, so you can tell at a glance where a PR is stuck. Comments now have their own Comments tab with a count, so new feedback is easy to spot. In conversation comments, each author gets a different color, which makes long threads easier to follow. _Learn more in the [Tracked pull requests](https://omnigent.ai/docs/interact/web-ui#tracked-pull-requests)._ ## 4. Reasoning Effort for Claude SDK and Codex SDK Agents Some tasks need careful thought and others just need a quick answer. Until now you could only set reasoning effort in native terminal sessions. Now you can also set it for Claude SDK and Codex SDK agents. Pick the effort level in the web composer, next to where you type your message. You don't need to restart anything or switch to the terminal. _Learn more in the [Reasoning effort](https://omnigent.ai/docs/build/models#reasoning-effort)._ ## 5. SSH Git Access for Sandboxed Agents Sandboxed agents often need your private repositories, but copying your SSH key into a sandbox gives up the isolation the sandbox is there to provide. With this release, sandboxed agents can use approved Git repositories over SSH and never receive the key. You decide which repositories are approved. Push access is optional and set per repository, so an agent can read one repo and push to another, with each permission granted on purpose. Full Changelog: https://github.com/omnigent-ai/omnigent/blob/main/CHANGELOG.md ## What's Next Install or upgrade Omnigent: ```bash uv tool install --python 3.12 omnigent # or: pip install "omnigent" ``` - Star the project and file issues on [GitHub](https://github.com/omnigent-ai/omnigent). - Join the conversation on our [Discord](https://discord.gg/omnigent). - Browse the [docs](https://omnigent.ai/docs) to go deeper. --- ## https://omnigent.ai/releases/0.18.0 # v0.18.0 _Released 2026-10-13_ · 3 min read · Omnigent maintainers Omnigent 0.18.0 is a major release focused on making Omnigent the home base for all of your agent work, from reviewing code wherever your repos live to catching up on everything your agents have said while you were away. The session panel now reviews GitLab merge requests and Azure DevOps pull requests alongside GitHub, a new `omnigent agent add` command installs reusable custom agents that follow you across hosts, Settings → Harnesses shows you exactly what each harness loads at startup, and a redesigned Inbox with All, Unread, and Awaiting response tabs makes it easy to find the replies that need you. ## 1. GitLab and Azure DevOps review in the session panel [Video: Reviewing a GitLab merge request, its checks, comments, and expanded diff in the session panel](https://github.com/user-attachments/assets/c5ff5710-2048-41ea-b4e8-fd9aa31e0601) ![GitHub, GitLab, and Azure DevOps review panels with example data on desktop and mobile](https://github.com/user-attachments/assets/3375a8a9-e8ca-4496-96ae-1dba4594fc26) Not every team keeps its code on GitHub, and until now that meant the session panel's review workflow only covered part of the picture. Omnigent 0.18.0 brings GitLab merge requests and Azure DevOps pull requests into the session panel with the same treatment GitHub pull requests get: checks, comments, and diffs, all right beside the conversation that produced the change. Tracking is automatic. When an agent creates or updates a merge request or pull request during a session, Omnigent picks it up and shows it in the panel, so you can review what the agent shipped, read the discussion, and watch checks land without leaving the session or switching between three different code hosts. _Learn more in the [Tracked pull requests](https://omnigent.ai/docs/interact/web-ui#tracked-pull-requests)._ ## 2. Reusable custom agents with `omnigent agent add` [Video: Installing, listing, and updating a reusable custom agent with the Omnigent CLI](https://github.com/user-attachments/assets/12a7bbe4-79ac-4bf3-bf23-7825bb18f681) Custom agents are most useful when you can reach for them again and again, not just on the machine where you first wrote the config. Omnigent 0.18.0 adds a small CLI for exactly that: run `omnigent agent add` to install a reusable custom agent, `omnigent agent list` to see what you have installed, and `omnigent agent remove` to clean up the ones you no longer need. If you built an agent with Create custom agent, you can import its bundle directly. Once installed, an agent stays in your new-session picker across hosts, so the same agent you tuned on your laptop is one click away when you start a session on a shared server or a cloud sandbox. [Video: Importing a custom-agent bundle and selecting the installed agent in the new-session picker](https://github.com/user-attachments/assets/289d47a6-3a2c-4ecc-ab51-56213358590e) _Learn more in the [Custom Agents](https://omnigent.ai/docs/use/custom-agents)._ ## 3. Harness inspection in Settings → Harnesses ![Claude Code harness details with MCP servers, skills, plugins, and startup settings](https://github.com/user-attachments/assets/31f6d7d1-8b54-4962-872a-4c9555239437) When an agent behaves unexpectedly, the first question is usually "what did this harness actually load?" Settings → Harnesses answers it. For each harness, you can browse the MCP servers it connects to, the skills and plugins it discovers, and the startup settings it launches with, all in one place in the UI. That makes it much faster to confirm a new MCP server or skill is wired up before you start a session, or to spot the missing plugin behind a tool call that never fires, without digging through config files on the host. ## 4. Inbox tabs and sidebar reordering ![Inbox with All, Unread, and Awaiting response tabs and cards grouped by recency](https://omnigent.ai/images/releases/0.18.0/inbox.png) Running several agents at once means replies pile up faster than you can read them. The Inbox in Omnigent 0.18.0 is built for catching up: switch between All, Unread, and Awaiting response tabs to focus on the sessions that actually need you, and expand cards grouped by recency to read a reply in place before deciding whether to jump into the session. The sidebar gets more flexible too. You can now drag pinned sessions to reorder them, so the sessions you care about most sit exactly where you expect them. [Video: Dragging pinned sessions into a new order and keeping that order after reloading](https://github.com/user-attachments/assets/5c692a10-8574-44cd-94d3-bfef71e826c2) Full Changelog: [https://github.com/omnigent-ai/omnigent/blob/main/CHANGELOG.md](https://github.com/omnigent-ai/omnigent/blob/main/CHANGELOG.md) ## What's Next Install or upgrade Omnigent: ```bash uv tool install --python 3.12 omnigent # or: pip install "omnigent" ``` - Star the project and file issues on [GitHub](https://github.com/omnigent-ai/omnigent). - Join the conversation on our [Discord](https://discord.gg/omnigent). - Browse the [docs](https://omnigent.ai/docs) to go deeper. --- ## https://omnigent.ai/releases/0.17.0 # v0.17.0 _Released 2026-10-06_ · 3 min read · Omnigent maintainers Omnigent 0.17.0 is a major release focused on making everyday work on a shared server smoother and safer, from signing in to the desktop app in the first place to keeping agent spend within a budget. Headline additions include browser-based OIDC sign-in with silent renewal for the desktop app, a set of keyboard shortcuts that let you move between workspace tabs, views and recent sessions without reaching for the mouse, and policies that can now enforce period-based cost budgets with richer per-event context. ## 1. OIDC sign-in through the system browser in the desktop app Signing in to a company-run Omnigent server should feel like signing in to any other SSO-protected app, not like configuring a client. The desktop app now hands OIDC sign-in off to your system browser, so you authenticate with your identity provider in the browser you already use, and the app picks up the session from there. Once you are in, tokens renew silently in the background, so you are not bounced back to a login screen in the middle of a session. The flow supports public PKCE clients and lets you specify explicit provider endpoints when discovery alone is not enough for your identity provider. Operators can also set a server name that the desktop app shows next to each host, so when you work across several servers you can tell at a glance which one you are connected to. _Learn more in [Single sign-on (OIDC)](https://omnigent.ai/docs/collaborate/auth#single-sign-on-oidc)._ ## 2. Keyboard shortcuts and a more scannable sidebar Driving an agent is faster when your hands stay on the keyboard. This release adds shortcuts for switching between workspace tabs, jumping to common views and reopening recent sessions, plus Ctrl+Shift+L to put the cursor straight back in the chat input from anywhere in the app. Together they make the move from reading a diff or a file to telling the agent what to do next a single keystroke. The sidebar's agent hierarchy has also been reworked to be easier to scan, so parent sessions and their sub-agents are clearer at a glance. And when you archive a session that was working on its own worktree, you can now choose to delete that worktree at the same time instead of cleaning it up by hand later. _Learn more in [Keyboard shortcuts](https://omnigent.ai/docs/interact/web-ui#keyboard-shortcuts)._ ## 3. Period-based cost budgets and richer PolicyEvent context Capping what a single session can spend is useful, but most teams think about spend per day or per week rather than per conversation. Policies can now enforce period-based cost budgets, so you can set a limit for a window of time and have Omnigent ask before crossing the thresholds you set and block expensive models once the limit is reached, without watching a dashboard. Custom policies also get more to work with. The `PolicyEvent` context now carries the `conversation_id`, so a policy can key its decisions and state to a specific session, and RESPONSE-phase checks receive a `turn_final` indicator that tells you whether the response you are inspecting is the last one in the turn, letting you reserve expensive or conclusive checks for the moment the agent is actually done. _Learn more in [Built-in policies: per-user period budgets](https://omnigent.ai/docs/policies/builtin#per-user-monthly-or-weekly-quarterly-yearly-budget) and the [PolicyEvent reference](https://omnigent.ai/docs/policies/custom#reference-policyevent)._ Full Changelog: https://github.com/omnigent-ai/omnigent/blob/main/CHANGELOG.md ## What's Next Install or upgrade Omnigent: ```bash uv tool install --python 3.12 omnigent # or: pip install "omnigent" ``` - Star the project and file issues on [GitHub](https://github.com/omnigent-ai/omnigent). - Join the conversation on our [Discord](https://discord.gg/omnigent). - Browse the [docs](https://omnigent.ai/docs) to go deeper. --- ## https://omnigent.ai/releases/0.16.0 # v0.16.0 _Released 2026-09-29_ · Omnigent maintainers Omnigent 0.16.0 updates the workspace browser, sandbox controls, and agent configuration. It also improves runner reconnection and harness reliability. Review the breaking changes below before upgrading. ## Major new features ### Unified workspace browser and file panel A unified workspace browser and compact file panel show badges for added, modified, and deleted files. Shared controls and folder indicators make changes easier to find while navigating the workspace. The desktop app adds Show in Finder for local files and an opt-in setting to open chat links in the in-app browser. Embedded browser sites can request local network access through a Chrome-style permission prompt. ![The unified workspace browser](https://github.com/user-attachments/assets/a84ab68d-a958-40b6-8178-ea0e7bc03c87) ([#8254](https://github.com/omnigent-ai/omnigent/pull/8254), [#8335](https://github.com/omnigent-ai/omnigent/pull/8335), [#6691](https://github.com/omnigent-ai/omnigent/pull/6691), [#8219](https://github.com/omnigent-ai/omnigent/pull/8219), [#8049](https://github.com/omnigent-ai/omnigent/pull/8049), [@ajayalfred](https://github.com/ajayalfred), [@nakazax](https://github.com/nakazax), [@fanzeyi](https://github.com/fanzeyi)) ### Sandbox edits and repository clones Linux agents can make disposable edits by setting `copy_on_write: true` on a sandbox `write_paths` entry. Edits stay in a disposable layer while the underlying files remain unchanged. Admins can opt into shallow clones to limit history or blobless clones to fetch file contents on demand. Daytona sessions also keep their workspace when a stopped or archived sandbox resumes. Learn more about [filesystem access](https://omnigent.ai/docs/reference/configuration/os-sandbox#filesystem) and [Git clone options](https://omnigent.ai/docs/reference/configuration/cloud-sandbox#git-clone-options). ([#7599](https://github.com/omnigent-ai/omnigent/pull/7599), [#8322](https://github.com/omnigent-ai/omnigent/pull/8322), [#7828](https://github.com/omnigent-ai/omnigent/pull/7828), [@mxatone](https://github.com/mxatone), [@dhruv0811](https://github.com/dhruv0811), [@wilbeibi](https://github.com/wilbeibi)) ### Admin defaults for sharing, allowed origins, and Slack setup Settings > Sharing now lets admins default new sessions to public, choosing off, sandbox-only, or all sessions. `OMNIGENT_WS_ALLOWED_ORIGINS` accepts `*.` entries to trust all subdomains of a domain. Operators can also pre-select the Slack setup agent and managed host through `OMNIGENT_SLACK_DEFAULT_AGENT_ID` and `OMNIGENT_SLACK_DEFAULT_HOST_TYPE`; users can change those choices before submitting setup. ![Settings > Sharing with the default visibility controls](https://github.com/user-attachments/assets/50159570-0d4a-4f11-84cc-3dc9ceafaffc) Learn more about [session sharing](https://omnigent.ai/docs/collaborate/auth#session-sharing) and [Slack setup defaults](https://omnigent.ai/docs/interact/slack#operator-set-setup-defaults). ([#8295](https://github.com/omnigent-ai/omnigent/pull/8295), [#8070](https://github.com/omnigent-ai/omnigent/pull/8070), [#6965](https://github.com/omnigent-ai/omnigent/pull/6965), [@dhruv0811](https://github.com/dhruv0811), [@natsuskaizen](https://github.com/natsuskaizen), [@bdchatham](https://github.com/bdchatham)) ### Pi prompt controls and native images Pi agents can set `executor.context_files: false` to skip automatically discovered context files while keeping their explicit instructions. With the regular Pi harness, `executor.system_prompt_mode: replace` replaces Pi's base system prompt with the agent's Omnigent instructions. Framework instructions remain in place, and skills and extensions can still contribute to the final prompt. MCP tool images and embedded-browser screenshots now reach supported harnesses as native images with accompanying text. Learn more about the [system prompt](https://omnigent.ai/docs/build/prompts#system-prompt). ([#8184](https://github.com/omnigent-ai/omnigent/pull/8184), [#8349](https://github.com/omnigent-ai/omnigent/pull/8349), [#7444](https://github.com/omnigent-ai/omnigent/pull/7444), [@PattaraS](https://github.com/PattaraS), [@Jomak-x](https://github.com/Jomak-x)) ## Breaking changes - Saved preferences and inference snapshots are now capped at 65,535 stored bytes: upgrading resets oversized preferences to defaults and clears snapshots that remain oversized after compression, and new oversized project-order saves or snapshots are rejected before session creation ([#8137](https://github.com/omnigent-ai/omnigent/pull/8137), [#8143](https://github.com/omnigent-ai/omnigent/pull/8143), [@dbczumar](https://github.com/dbczumar)) - Only session owners can manage MCP servers or replace agent bundles; collaborators who previously edited MCP configuration now get read-only visibility and must ask the owner to make changes ([#8481](https://github.com/omnigent-ai/omnigent/pull/8481), [@dhruv0811](https://github.com/dhruv0811)) ## Bug fixes - 🔌 Brief runner drops (Wi-Fi roam, VPN stall, sleep/resume, replica failover) no longer fail turns with "Runner disconnected unexpectedly", runners reconnect promptly, and host launches no longer spawn duplicate runners ([#8093](https://github.com/omnigent-ai/omnigent/pull/8093), [#8094](https://github.com/omnigent-ai/omnigent/pull/8094), [#8152](https://github.com/omnigent-ai/omnigent/pull/8152), [#8413](https://github.com/omnigent-ai/omnigent/pull/8413), [#8085](https://github.com/omnigent-ai/omnigent/pull/8085), [@yaoharry](https://github.com/yaoharry), [@dbczumar](https://github.com/dbczumar), [@daniellok-db](https://github.com/daniellok-db)) - 🖥️ Codex terminals recover without restarting a healthy backend, keep their final screen and exit code, survive launcher sign-in prompts with the link shown in chat, and cold resumes no longer lose tool results across compaction; sub-agents no longer flash "failed" on newer Codex versions ([#7442](https://github.com/omnigent-ai/omnigent/pull/7442), [#8233](https://github.com/omnigent-ai/omnigent/pull/8233), [#8040](https://github.com/omnigent-ai/omnigent/pull/8040), [#8271](https://github.com/omnigent-ai/omnigent/pull/8271), [#8309](https://github.com/omnigent-ai/omnigent/pull/8309), [@yaoharry](https://github.com/yaoharry), [@dbczumar](https://github.com/dbczumar)) - 🤖 Claude Code sessions use a Unix socket instead of a loopback port per MCP helper, treat `/exit` as a clean stop, show Claude's own API error text, aren't failed by a background sub-agent, and mark lost messages undelivered instead of duplicating the next one ([#8079](https://github.com/omnigent-ai/omnigent/pull/8079), [#8364](https://github.com/omnigent-ai/omnigent/pull/8364), [#8097](https://github.com/omnigent-ai/omnigent/pull/8097), [#8103](https://github.com/omnigent-ai/omnigent/pull/8103), [#8186](https://github.com/omnigent-ai/omnigent/pull/8186), [@dbczumar](https://github.com/dbczumar), [@yaoharry](https://github.com/yaoharry)) - 🧠 Pi on the Databricks AI Gateway lists Claude, OpenAI, and Gemini models, sub-agents no longer report false launch failures, and new sessions accept the first message during provider setup ([#8228](https://github.com/omnigent-ai/omnigent/pull/8228), [#8200](https://github.com/omnigent-ai/omnigent/pull/8200), [#8424](https://github.com/omnigent-ai/omnigent/pull/8424), [@dbczumar](https://github.com/dbczumar), [@serena-ruan](https://github.com/serena-ruan)) - ✍️ Composer fixes: cancel or edit an unsent message while a model switch finishes, pick skills with `/` anywhere in a prompt, and Files panel search finds tracked files in repositories of any size ([#8039](https://github.com/omnigent-ai/omnigent/pull/8039), [#8087](https://github.com/omnigent-ai/omnigent/pull/8087), [#8409](https://github.com/omnigent-ai/omnigent/pull/8409), [@dbczumar](https://github.com/dbczumar), [@serena-ruan](https://github.com/serena-ruan)) ## Desktop onboarding preview We're working on a redesigned desktop onboarding flow to make setup easier for new and returning users. This preview is still in development and hasn't shipped in a desktop release yet. We'd love to hear your thoughts on the new flow and what would make getting started smoother. Share your feedback on [Discord](https://discord.gg/omnigent). ![Preview of the redesigned desktop onboarding flow](https://github.com/user-attachments/assets/7ff29e03-2567-47e2-8e35-ebb974236086) ([#8119](https://github.com/omnigent-ai/omnigent/pull/8119), [#8126](https://github.com/omnigent-ai/omnigent/pull/8126), [#8195](https://github.com/omnigent-ai/omnigent/pull/8195), [#8202](https://github.com/omnigent-ai/omnigent/pull/8202), [#8290](https://github.com/omnigent-ai/omnigent/pull/8290), [#8277](https://github.com/omnigent-ai/omnigent/pull/8277), [#8311](https://github.com/omnigent-ai/omnigent/pull/8311), [@hzub](https://github.com/hzub), [@zhengwin](https://github.com/zhengwin)) ## Other contributions [#8055](https://github.com/omnigent-ai/omnigent/pull/8055), [#8072](https://github.com/omnigent-ai/omnigent/pull/8072), [@ajayalfred](https://github.com/ajayalfred); [#5917](https://github.com/omnigent-ai/omnigent/pull/5917), [@Arshgill01](https://github.com/Arshgill01); [#7495](https://github.com/omnigent-ai/omnigent/pull/7495), [@arthivjkumar](https://github.com/arthivjkumar); [#8439](https://github.com/omnigent-ai/omnigent/pull/8439), [@chameleon82](https://github.com/chameleon82); [#8485](https://github.com/omnigent-ai/omnigent/pull/8485), [@christianclaudio](https://github.com/christianclaudio); [#8002](https://github.com/omnigent-ai/omnigent/pull/8002), [#8029](https://github.com/omnigent-ai/omnigent/pull/8029), [#8066](https://github.com/omnigent-ai/omnigent/pull/8066), [#8108](https://github.com/omnigent-ai/omnigent/pull/8108), [@daniellok-db](https://github.com/daniellok-db); [#8042](https://github.com/omnigent-ai/omnigent/pull/8042), [#8053](https://github.com/omnigent-ai/omnigent/pull/8053), [#8063](https://github.com/omnigent-ai/omnigent/pull/8063), [#8074](https://github.com/omnigent-ai/omnigent/pull/8074), [#8090](https://github.com/omnigent-ai/omnigent/pull/8090), [#8092](https://github.com/omnigent-ai/omnigent/pull/8092), [#8138](https://github.com/omnigent-ai/omnigent/pull/8138), [#8270](https://github.com/omnigent-ai/omnigent/pull/8270), [#8320](https://github.com/omnigent-ai/omnigent/pull/8320), [#8362](https://github.com/omnigent-ai/omnigent/pull/8362), [#8416](https://github.com/omnigent-ai/omnigent/pull/8416), [#8469](https://github.com/omnigent-ai/omnigent/pull/8469), [#8494](https://github.com/omnigent-ai/omnigent/pull/8494), [@dbczumar](https://github.com/dbczumar); [#7802](https://github.com/omnigent-ai/omnigent/pull/7802), [#8327](https://github.com/omnigent-ai/omnigent/pull/8327), [@dependabot[bot]](https://github.com/apps/dependabot); [#8216](https://github.com/omnigent-ai/omnigent/pull/8216), [#8313](https://github.com/omnigent-ai/omnigent/pull/8313), [#8316](https://github.com/omnigent-ai/omnigent/pull/8316), [#8319](https://github.com/omnigent-ai/omnigent/pull/8319), [#8325](https://github.com/omnigent-ai/omnigent/pull/8325), [#8330](https://github.com/omnigent-ai/omnigent/pull/8330), [@dhruv0811](https://github.com/dhruv0811); [#7871](https://github.com/omnigent-ai/omnigent/pull/7871), [#7872](https://github.com/omnigent-ai/omnigent/pull/7872), [@dipeshbabu](https://github.com/dipeshbabu); [#2405](https://github.com/omnigent-ai/omnigent/pull/2405), [@dosenr](https://github.com/dosenr); [#7977](https://github.com/omnigent-ai/omnigent/pull/7977), [#8041](https://github.com/omnigent-ai/omnigent/pull/8041), [#8051](https://github.com/omnigent-ai/omnigent/pull/8051), [#8056](https://github.com/omnigent-ai/omnigent/pull/8056), [#8057](https://github.com/omnigent-ai/omnigent/pull/8057), [#8059](https://github.com/omnigent-ai/omnigent/pull/8059), [#8061](https://github.com/omnigent-ai/omnigent/pull/8061), [@fanzeyi](https://github.com/fanzeyi); [#8017](https://github.com/omnigent-ai/omnigent/pull/8017), [#8026](https://github.com/omnigent-ai/omnigent/pull/8026), [#8031](https://github.com/omnigent-ai/omnigent/pull/8031), [#8106](https://github.com/omnigent-ai/omnigent/pull/8106), [#8132](https://github.com/omnigent-ai/omnigent/pull/8132), [#8201](https://github.com/omnigent-ai/omnigent/pull/8201), [#8444](https://github.com/omnigent-ai/omnigent/pull/8444), [@hzub](https://github.com/hzub); [#8140](https://github.com/omnigent-ai/omnigent/pull/8140), [#8146](https://github.com/omnigent-ai/omnigent/pull/8146), [#8149](https://github.com/omnigent-ai/omnigent/pull/8149), [#8247](https://github.com/omnigent-ai/omnigent/pull/8247), [@Leemoonsoo](https://github.com/Leemoonsoo); [#6770](https://github.com/omnigent-ai/omnigent/pull/6770), [@nhsdb](https://github.com/nhsdb); [#5519](https://github.com/omnigent-ai/omnigent/pull/5519), [#7166](https://github.com/omnigent-ai/omnigent/pull/7166), [#7479](https://github.com/omnigent-ai/omnigent/pull/7479), [#7512](https://github.com/omnigent-ai/omnigent/pull/7512), [#7602](https://github.com/omnigent-ai/omnigent/pull/7602), [#7673](https://github.com/omnigent-ai/omnigent/pull/7673), [#7698](https://github.com/omnigent-ai/omnigent/pull/7698), [#7704](https://github.com/omnigent-ai/omnigent/pull/7704), [#7708](https://github.com/omnigent-ai/omnigent/pull/7708), [#8014](https://github.com/omnigent-ai/omnigent/pull/8014), [#8166](https://github.com/omnigent-ai/omnigent/pull/8166), [#8196](https://github.com/omnigent-ai/omnigent/pull/8196), [#8403](https://github.com/omnigent-ai/omnigent/pull/8403); [#8028](https://github.com/omnigent-ai/omnigent/pull/8028), [#8035](https://github.com/omnigent-ai/omnigent/pull/8035), [@omnigent-ci[bot]](https://github.com/apps/omnigent-ci); [#7991](https://github.com/omnigent-ai/omnigent/pull/7991), [#8114](https://github.com/omnigent-ai/omnigent/pull/8114), [#8192](https://github.com/omnigent-ai/omnigent/pull/8192), [#8206](https://github.com/omnigent-ai/omnigent/pull/8206), [#8207](https://github.com/omnigent-ai/omnigent/pull/8207), [#8262](https://github.com/omnigent-ai/omnigent/pull/8262), [#8275](https://github.com/omnigent-ai/omnigent/pull/8275), [@PattaraS](https://github.com/PattaraS); [#8024](https://github.com/omnigent-ai/omnigent/pull/8024), [#8025](https://github.com/omnigent-ai/omnigent/pull/8025), [#8098](https://github.com/omnigent-ai/omnigent/pull/8098), [#8113](https://github.com/omnigent-ai/omnigent/pull/8113), [#8117](https://github.com/omnigent-ai/omnigent/pull/8117), [#8120](https://github.com/omnigent-ai/omnigent/pull/8120), [#8158](https://github.com/omnigent-ai/omnigent/pull/8158), [#8178](https://github.com/omnigent-ai/omnigent/pull/8178), [#8185](https://github.com/omnigent-ai/omnigent/pull/8185), [#8197](https://github.com/omnigent-ai/omnigent/pull/8197), [#8256](https://github.com/omnigent-ai/omnigent/pull/8256), [#8417](https://github.com/omnigent-ai/omnigent/pull/8417), [#8425](https://github.com/omnigent-ai/omnigent/pull/8425), [#8428](https://github.com/omnigent-ai/omnigent/pull/8428), [#8431](https://github.com/omnigent-ai/omnigent/pull/8431), [#8432](https://github.com/omnigent-ai/omnigent/pull/8432), [#8510](https://github.com/omnigent-ai/omnigent/pull/8510), [@serena-ruan](https://github.com/serena-ruan); [#8160](https://github.com/omnigent-ai/omnigent/pull/8160), [@terrytangyuan](https://github.com/terrytangyuan); [#8351](https://github.com/omnigent-ai/omnigent/pull/8351), [#8410](https://github.com/omnigent-ai/omnigent/pull/8410), [@TomeHirata](https://github.com/TomeHirata); [#7731](https://github.com/omnigent-ai/omnigent/pull/7731), [@trungnguyen21](https://github.com/trungnguyen21); [#8046](https://github.com/omnigent-ai/omnigent/pull/8046), [@tyler-lynch](https://github.com/tyler-lynch); [#8215](https://github.com/omnigent-ai/omnigent/pull/8215), [@vasvlasov](https://github.com/vasvlasov); [#7975](https://github.com/omnigent-ai/omnigent/pull/7975), [#8006](https://github.com/omnigent-ai/omnigent/pull/8006), [#8058](https://github.com/omnigent-ai/omnigent/pull/8058), [#8095](https://github.com/omnigent-ai/omnigent/pull/8095), [#8096](https://github.com/omnigent-ai/omnigent/pull/8096), [#8240](https://github.com/omnigent-ai/omnigent/pull/8240), [#8260](https://github.com/omnigent-ai/omnigent/pull/8260), [#8263](https://github.com/omnigent-ai/omnigent/pull/8263), [#8266](https://github.com/omnigent-ai/omnigent/pull/8266), [#8503](https://github.com/omnigent-ai/omnigent/pull/8503), [@yaoharry](https://github.com/yaoharry); [#8062](https://github.com/omnigent-ai/omnigent/pull/8062), [#8081](https://github.com/omnigent-ai/omnigent/pull/8081), [#8110](https://github.com/omnigent-ai/omnigent/pull/8110), [#8118](https://github.com/omnigent-ai/omnigent/pull/8118), [#8136](https://github.com/omnigent-ai/omnigent/pull/8136), [#8141](https://github.com/omnigent-ai/omnigent/pull/8141), [#8159](https://github.com/omnigent-ai/omnigent/pull/8159), [#8161](https://github.com/omnigent-ai/omnigent/pull/8161), [#8162](https://github.com/omnigent-ai/omnigent/pull/8162), [#8163](https://github.com/omnigent-ai/omnigent/pull/8163), [#8174](https://github.com/omnigent-ai/omnigent/pull/8174), [#8179](https://github.com/omnigent-ai/omnigent/pull/8179), [#8182](https://github.com/omnigent-ai/omnigent/pull/8182), [#8191](https://github.com/omnigent-ai/omnigent/pull/8191), [#8193](https://github.com/omnigent-ai/omnigent/pull/8193), [#8194](https://github.com/omnigent-ai/omnigent/pull/8194), [#8198](https://github.com/omnigent-ai/omnigent/pull/8198), [#8220](https://github.com/omnigent-ai/omnigent/pull/8220), [#8221](https://github.com/omnigent-ai/omnigent/pull/8221), [#8222](https://github.com/omnigent-ai/omnigent/pull/8222), [#8223](https://github.com/omnigent-ai/omnigent/pull/8223), [#8224](https://github.com/omnigent-ai/omnigent/pull/8224), [#8225](https://github.com/omnigent-ai/omnigent/pull/8225), [#8226](https://github.com/omnigent-ai/omnigent/pull/8226), [#8236](https://github.com/omnigent-ai/omnigent/pull/8236), [#8255](https://github.com/omnigent-ai/omnigent/pull/8255), [@zhengwin](https://github.com/zhengwin) ### 💜 Thanks to our community This release was shaped by the people who filed issues, opened PRs, and talked through feature requests with us on our Discord! Thank you for building omnigent with us, keep the bug reports, ideas and contributions coming :) [@ajayalfred](https://github.com/ajayalfred), [@Arshgill01](https://github.com/Arshgill01), [@arthivjkumar](https://github.com/arthivjkumar), [@bdchatham](https://github.com/bdchatham), [@chameleon82](https://github.com/chameleon82), [@christianclaudio](https://github.com/christianclaudio), [@daniellok-db](https://github.com/daniellok-db), [@dbczumar](https://github.com/dbczumar), [@dependabot[bot]](https://github.com/apps/dependabot), [@dhruv0811](https://github.com/dhruv0811), [@dipeshbabu](https://github.com/dipeshbabu), [@dosenr](https://github.com/dosenr), [@fanzeyi](https://github.com/fanzeyi), [@hzub](https://github.com/hzub), [@Jomak-x](https://github.com/Jomak-x), [@Leemoonsoo](https://github.com/Leemoonsoo), [@mxatone](https://github.com/mxatone), [@nakazax](https://github.com/nakazax), [@natsuskaizen](https://github.com/natsuskaizen), [@nhsdb](https://github.com/nhsdb), [@omnigent-ci[bot]](https://github.com/apps/omnigent-ci), [@PattaraS](https://github.com/PattaraS), [@serena-ruan](https://github.com/serena-ruan), [@terrytangyuan](https://github.com/terrytangyuan), [@TomeHirata](https://github.com/TomeHirata), [@trungnguyen21](https://github.com/trungnguyen21), [@tyler-lynch](https://github.com/tyler-lynch), [@vasvlasov](https://github.com/vasvlasov), [@wilbeibi](https://github.com/wilbeibi), [@yaoharry](https://github.com/yaoharry), [@zhengwin](https://github.com/zhengwin) Full Changelog: https://github.com/omnigent-ai/omnigent/blob/main/CHANGELOG.md ## Install or upgrade ```bash uv tool install --python 3.12 --upgrade omnigent ``` For a pip-managed environment, use `pip install --upgrade omnigent`. - Read the [GitHub release](https://github.com/omnigent-ai/omnigent/releases/tag/v0.16.0). - Join the conversation on [Discord](https://discord.gg/omnigent). - Browse the [docs](https://omnigent.ai/docs). --- ## https://omnigent.ai/releases/0.15.0 # v0.15.0 _Released 2026-09-24_ · 4 min read · Omnigent maintainers Omnigent 0.15.0 is a major release focused on the everyday session experience, bringing a new native harness, lots of quality-of-life features for session management, and improved chat UX within sessions. Here are some of the highlights: ## 1. Devin as a native harness ![The real Devin TUI mirrored into the Omnigent web UI, writing a Rust hello-world script alongside the working-folder panel](https://omnigent.ai/images/releases/0.15.0/devin-harness.png) Running a coding agent through a translation layer means losing the details of its own interface. Devin now runs as a native harness in Omnigent: `omni devin` mirrors the real Devin TUI into the web UI, the same way the Claude Code and Codex native harnesses do, so what you see in the browser is the actual Devin experience rather than an approximation of it. The previous ACP-based Devin harness is replaced by the native one. The old harness id continues to resolve, but sessions now run through the native path, so existing configurations keep working without changes. _Learn more in the [Devin](https://omnigent.ai/docs/build/harnesses/configuration#devin)._ ## 2. Curated model catalogs for Pi, custom ACP agents, and managed sandboxes ![New session composer showing the selected Pi model with a tooltip naming the harness, model, and gateway connection](https://omnigent.ai/images/releases/0.15.0/model-catalog.png) Picking a model should not mean scrolling through every identifier a gateway happens to expose. Pi, custom ACP agents, and managed Kubernetes / Agent Sandbox sessions can now show configured model shortlists, so your team sees the handful of models you actually want them to use. Catalogs are configured per harness, each with its own gateway, and you can optionally turn on live gateway discovery to populate the list from what the gateway currently serves instead of maintaining it by hand. _Learn more in the [Choose a model](https://omnigent.ai/docs/build/models#choose-a-model)._ ## 3. A richer Composer ![Typing '/plan' in the Composer to discover and insert the skills available to the session](https://omnigent.ai/images/releases/0.15.0/skills-composer.png) The Composer is where you spend most of your time in a session, and this release makes it do more. You can preview attached images before you send them, and Claude Code and Codex sessions accept more types of files as attachments. Queued messages and active sub-agent work are now managed directly from the Composer, so you can reorder, edit, or check on in-flight work without leaving the input. Skills are also easier to find: from the new session page, type `/` (or `$` for Codex) to discover and insert the skills available to that session. _Learn more in the [Multi-modal input](https://omnigent.ai/docs/interact/web-ui#multi-modal-input)._ ## 4. Side chats ![A side chat answering a question in the right panel while the main session transcript stays untouched](https://omnigent.ai/images/releases/0.15.0/side-chats.png) Sometimes you want to ask a quick question about what the agent is doing without steering the main conversation off course. Side chats give you a separate thread inside your session: the side chat sees the context from your session, but nothing you discuss there is added back to it, so your primary transcript stays focused. To start one, use the "+" button in the chat pane's right panel. _Learn more in the [Side questions with `/btw`](https://omnigent.ai/docs/interact/web-ui#side-questions-with-btw)._ ## 5. Improved session management [Video: Dragging projects in the sidebar to rearrange them into a persistent manual order](https://omnigent.ai/images/releases/0.15.0/project-ordering.webm) As your session list grows, keeping it organized matters. You can now export a session straight from its actions menu, arrange projects in the order you want rather than the order they were created, and see pull request titles in the session PR dropdown instead of bare numbers, making it easier to tell at a glance which PR a session is tracking. _Learn more in the [Session management](https://omnigent.ai/docs/interact/web-ui#session-management)._ ## 6. Reverse-proxy subpath deployment with `--base-path` Not every deployment gets its own hostname. Omnigent can now serve the web UI and API behind a reverse-proxy subpath, so you can mount it alongside other internal tools under a shared domain. Set `OMNIGENT_WEB_BASE_PATH` or pass `omnigent server --base-path` and both the UI and the API resolve correctly under that prefix. Full Changelog: https://github.com/omnigent-ai/omnigent/blob/main/CHANGELOG.md ## What's Next Install or upgrade Omnigent: ```bash uv tool install --python 3.12 omnigent # or: pip install "omnigent" ``` - Star the project and file issues on [GitHub](https://github.com/omnigent-ai/omnigent). - Join the conversation on our [Discord](https://discord.gg/omnigent). - Browse the [docs](https://omnigent.ai/docs) to go deeper. --- ## https://omnigent.ai/releases/0.14.0 # v0.14.0 _Released 2026-09-15_ · 3 min read · Omnigent maintainers This release makes it easier to work across GitHub repositories, configure sessions, and keep track of ongoing work. It also adds deployment options and improves session recovery. ## Work across repositories [Video: Switching between linked pull requests in a session's GitHub panel](https://omnigent.ai/images/releases/0.14.0/github-pr-tracking.mp4) - **Track multiple GitHub PRs in one session**, including PRs from different repositories. - **Switch between PR summaries and diffs**, and manually link or unlink PRs. - **Start a managed sandbox with multiple GitHub repositories:** choose repositories and branches when creating a session; Omnigent clones them in parallel so the agent can work across them. ## A smoother chat interface [Video: Choosing an agent, model, and reasoning effort in the redesigned chat composer](https://omnigent.ai/images/releases/0.14.0/composer.mp4) - **Configure sessions in one place.** The redesigned composer brings workspace, agent, model, reasoning effort, and permission controls together, with improved folder browsing. - **Find sessions faster.** Search session names with `⌘⌥S` on macOS or `Ctrl+Alt+S` on Windows/Linux. - **Open the model picker from your keyboard** with `⌘⇧M` on macOS or `Ctrl+Shift+M` on Windows/Linux. - **Inspect running background commands** from a compact badge beside the composer's workspace controls. - **Manage context in Claude SDK sessions.** See context-window usage and run `/compact` to summarize the conversation and free space, including with Claude Pro/Max logins. ## Expanded sandbox and database support - **Gensee:** provision and clean up managed sandboxes. - **Kubernetes:** run sandboxes on dedicated node pools using tolerations. - **CockroachDB:** use it as Omnigent's application database. ## Reliability fixes - Pending approvals and requests for user input remain answerable across server and runner restarts. - Native Codex sessions resume more reliably, preserving permission overrides and selected reasoning effort. - Working indicators more accurately reflect agent activity, and completed answers remain visible when background work resumes. - Improved interruption behavior and fixes for archived sessions reappearing. - More consistent validation of agent settings and preservation of reviewed tool arguments. [Full changelog](https://github.com/omnigent-ai/omnigent/blob/main/CHANGELOG.md) ## What's Next Install or upgrade Omnigent: ```bash uv tool install --python 3.12 omnigent # or: pip install "omnigent" ``` - Star the project and file issues on [GitHub](https://github.com/omnigent-ai/omnigent). - Join the conversation on our [Discord](https://discord.gg/omnigent). - Browse the [docs](https://omnigent.ai/docs) to go deeper. --- ## https://omnigent.ai/releases/0.13.0 # v0.13.0 _Released 2026-09-09_ · 3 min read · Omnigent maintainers Omnigent 0.13.0 is a major release focused on tightening the loop between your agents, your code, and the infrastructure they run on — from watching a branch's pull request come together live inside the workspace to running every session in its own self-reclaiming sandbox. A new GitHub panel puts the PR, its checks, and a full diff right beside the agent, Canvas graduates into a built-in per-project board of live session cards, and a wave of managed-sandbox providers, faster session switching, and new harnesses round out the release. ## 1. GitHub PR panel in the session workspace [Video: Opening a pull request summary and switching to its file-by-file changes in the GitHub panel](https://omnigent.ai/images/releases/0.13.0/github-pr-panel.mp4) A new GitHub panel in the session workspace rail shows the branch's pull request without leaving Omnigent — the summary, comments, CI checks, and a stacked diff with a file-tree navigator, all refreshing live as the agent pushes. You watch the PR take shape in the same window where the work is happening. To use it, start a New Chat from a repository with an open pull request, then open the panel and switch between the split Summary and Changes tabs to move from the overview to the file-by-file diff. In managed sandboxes with GitHub Connect configured, private clones and in-session `git` and `gh` authenticate as your connected account, so pushes and PR actions run as you. ## 2. Canvas as a built-in page ![Canvas page organizing live Omnigent sessions into draggable project cards](https://omnigent.ai/images/releases/0.13.0/canvas.png) Canvas is now a built-in page rather than a separate extension: a per-project board of draggable session cards, each showing live status, its worktree branch, and a link to its PR. It gives you a spatial, at-a-glance view of everything in flight on a project. From the board you can open a session's transcript or start a brand-new session in place, so Canvas becomes the launch point for your work rather than just a status display. Enable the built-in page with `OMNIGENT_FEATURES=canvas`. ## 3. New managed-sandbox providers Omnigent gains two new ways to run managed sandboxes. You can run each host in a local libkrun microVM by setting `sandbox.provider: microsandbox`, which gives you sub-second boots and in-place resume, or run hosts as self-reclaiming Kubernetes agent-sandbox resources that stay alive while an agent is working, suspend when idle, and resume later with their workspace intact. Both paths are built to avoid waste: sandboxes that remain offline can be reaped automatically, so idle compute doesn't linger. _Learn more in the [Cloud Sandbox Host](https://omnigent.ai/docs/deploy/cloud-sandbox-host)._ ## 4. Instant session switching Sessions now feel instant. New conversations open immediately while the server catches up in the background, and long transcripts virtualize so switching between them stays fast even when history is deep. The sidebar keeps up too: rows label themselves with your first prompt and highlight the moment you click them, and new sessions can be auto-named for you — a behavior you can toggle in Settings. _Learn more in the [Session management](https://omnigent.ai/docs/interact/web-ui#session-management)._ ## 5. More harnesses and enterprise CLIs Omnigent adds more ways to bring your preferred agents along. Jcode joins as a first-class ACP harness, and the `EXTRA_HARNESS_CLIS` build-arg bakes extra harness CLIs — goose, jcode, cursor, kimi, and more — into the host image, so you can extend the image without forking the Dockerfile. Enterprises get two more building blocks: an opt-in OAuth 2.0 client-credentials grant, and a HashiCorp Vault Transit backend for the credential store. _Learn more in the [Harnesses supported via ACP](https://omnigent.ai/docs/build/harnesses/supported#harnesses-supported-via-acp)._ ## Before upgrading - Managed terminals now require tmux 3.3 or newer. Upgrade tmux before launching a managed terminal. - The standalone `omnigent-canvas` extension has been removed. Enable the built-in Canvas page with `OMNIGENT_FEATURES=canvas` instead. - `omnigent update` is deprecated. Use `omnigent upgrade` (or `omni upgrade`) instead. Full Changelog: https://github.com/omnigent-ai/omnigent/blob/main/CHANGELOG.md ## What's Next Upgrade an existing Omnigent installation: ```bash omni upgrade ``` Or install Omnigent for the first time: ```bash uv tool install --python 3.12 omnigent # or: pip install "omnigent" ``` - Star the project and file issues on [GitHub](https://github.com/omnigent-ai/omnigent). - Join the conversation on our [Discord](https://discord.gg/omnigent). - Browse the [docs](https://omnigent.ai/docs) to go deeper. --- ## https://omnigent.ai/releases/0.12.0 # v0.12.0 _Released 2026-09-01_ · 3 min read · Omnigent maintainers Omnigent 0.12.0 is a major release focused on meeting your existing tools and workflows where they already live, from pulling your current CLI conversations into the web UI to running your host as an always-on background service. Projects now shape every new session with their own agent, workspace, and git defaults; the mobile and wide-screen UI gets a top-to-bottom refresh; and a wave of new harnesses and model controls give you finer say over which model runs and how hard it thinks. ## 1. Import your existing local CLI sessions If you already have a trail of conversations in Claude Code, Codex, or another CLI, you no longer have to leave them behind. From the web UI you can pull your recent CLI conversations straight into Omnigent, and each one keeps its original title so you can find it again. This means the work you've already done comes with you — pick up an earlier session in Omnigent instead of starting from scratch. _Learn more in the [Import existing chats](https://omnigent.ai/docs/use/coding-agents#import-existing-chats)._ ## 2. Projects that drive new sessions Projects now shape the sessions you start inside them. Name a `project_id` when you create a session and it inherits that project's agent, workspace, and git defaults, so a new session starts already pointed at the right code and configuration. You can also move an existing session into a project straight from the folder tag in its title. And when a session is started from a project, it now uses the project's configured agent and directory instead of silently swapping to something else. _Learn more in the [Projects](https://omnigent.ai/docs/interact/web-ui#projects)._ ## 3. Refreshed mobile and wide-screen UI The interface has been reworked to feel right on every screen size. On mobile you get a ChatGPT-style drawer and full-screen search; on ultra-wide displays, Omnigent now makes better use of all that horizontal space. You also get more control over how it looks and behaves: export and import your appearance settings, switch to a heavier code font, jump around long messages with a markdown table of contents, and choose whether Enter or ⌘-Enter sends your message. _Learn more in the [Appearance settings](https://omnigent.ai/docs/interact/web-ui#appearance-settings)._ ## 4. More harnesses and finer model control Omnigent 0.12.0 broadens what you can run and how you run it. Antigravity arrives with the `omni agy` shortcut and `agy-native` aliases, while Devin and Grok Build now show up in the New Chat picker. Setup also offers a "Pi — use own login" option. On the model side, the pi harness takes a reasoning-effort setting that you can even change mid-session with `/effort`, self-hosted models like Ollama and vLLM support custom per-token pricing, and when you fork a session you can give the fork its own model, effort level, and approval mode. _Learn more in the [Models & Credentials](https://omnigent.ai/docs/build/models)._ ## 5. Run your host as a service Your host can now run as a proper background service. Use `omni host enable` and `omni host disable` to install a per-user service on macOS or Linux, so your host is there whenever you need it. Running `omnigent host` or `omnigent start` opens the web UI for you automatically. ACP sub-agents such as Devin now appear as their own child sessions, with their tool calls shown right in the transcript, so you can see exactly what each one is doing. Full Changelog: https://github.com/omnigent-ai/omnigent/blob/main/CHANGELOG.md ## What's Next Install or upgrade Omnigent: ```bash uv tool install --python 3.12 omnigent # or: pip install "omnigent" ``` - Star the project and file issues on [GitHub](https://github.com/omnigent-ai/omnigent). - Join the conversation on our [Discord](https://discord.gg/omnigent). - Browse the [docs](https://omnigent.ai/docs) to go deeper. --- ## https://omnigent.ai/releases/0.10.0 # v0.10.0 _Released 2026-08-19_ · 3 min read · Omnigent maintainers Omnigent v0.10.0 is a major release focused on scaling out where and how your agents run, from spreading sandboxes across multiple providers to switching conversations instantly. This release adds first-class multi-provider sandboxing with a new Blaxel backend and self-restarting Kubernetes runners, promotes Devin to a built-in harness alongside easier Copilot and ACP-agent setup, introduces a Usage page for tracking cost and spend, and makes session management — moving, forking, and bulk-cleaning sessions — something you can drive entirely from the web UI. ## 1. Multiple sandbox providers, Blaxel, and self-healing Kubernetes runners ![Blaxel sandbox provider panels showing session configuration and sandbox selection](https://omnigent.ai/images/releases/0.10.0/blaxel-provider.png) You can now run several sandbox providers side by side. The new `sandbox.providers` configuration lets you register more than one backend and expose a picker option for each, so different sessions can land on different infrastructure without reconfiguring the server. This release also adds a new Blaxel provider to the mix. On Kubernetes, runners now launch as Jobs, which means a crashed runner restarts automatically instead of leaving the session stranded. To get a cluster up quickly, there's an ArgoCD quick-start overlay you can deploy from. _Learn more in the [Cloud Sandbox Host](https://omnigent.ai/docs/deploy/cloud-sandbox-host)._ ## 2. Devin as a built-in harness, plus easier ACP and Copilot setup Devin is now a built-in harness, so you can pick it directly rather than wiring it up by hand. Configured ACP agents — Devin, Grok Build, and others — show up automatically in `omni setup` and in the New Chat picker, making them selectable the moment they're configured. The Copilot harness now authenticates using your existing `gh auth login` session, including GitHub Enterprise hosts, so there's no separate credential dance to get it working. _Learn more in the [Configuration](https://omnigent.ai/docs/build/harnesses/configuration)._ ## 3. Usage page for cost and spend tracking ![Usage page showing session costs, daily spend timeline, and breakdowns by harness and model](https://omnigent.ai/images/releases/0.10.0/usage-page.png) A new Usage page gives you visibility into what your sessions cost. It tracks session costs and daily spend, and breaks spending down by harness and by model, so you can see where your budget is going at a glance. For deployments, you can gate features — including the Usage page itself and web-driven harness setup — behind the `OMNIGENT_FEATURES` setting, letting you control which capabilities are exposed to your users. ## 4. Manage sessions from the web UI [Video: Promoting a sub-agent to a top-level session by forking it from the Agents panel](https://omnigent.ai/images/releases/0.10.0/fork-subagent.mp4) Session management is now a full web-UI workflow. You can move a running session to another machine, fork a sub-agent up into its own top-level session, and leave a session someone has shared with you — all without dropping to the terminal. When it's time to clean up, you can bulk-move or bulk-delete sessions, with optional worktree-branch cleanup so leftover branches don't pile up behind you. _Learn more in the [Session management](https://omnigent.ai/docs/interact/web-ui#session-management)._ ## 5. Instant conversation and terminal switching [Video: Switching between conversations instantly with background streams kept alive](https://omnigent.ai/images/releases/0.10.0/instant-switching.mp4) Switching between conversations and terminals is now instant. Background conversation streams stay open instead of tearing down and reconnecting, so jumping back into a session is immediate. Web terminals now attach over loopback when the runner is local, which cuts keystroke echo from around 250 ms down to under 10 ms — typing in a local session feels native again. Full Changelog: https://github.com/omnigent-ai/omnigent/blob/main/CHANGELOG.md ## What's Next Install or upgrade Omnigent: ```bash uv tool install --python 3.12 omnigent # or: pip install "omnigent" ``` - Star the project and file issues on [GitHub](https://github.com/omnigent-ai/omnigent). - Join the conversation on our [Discord](https://discord.gg/omnigent). - Browse the [docs](https://omnigent.ai/docs) to go deeper. --- ## https://omnigent.ai/releases/0.9.0 # v0.9.0 _Released 2026-08-11_ · 3 min read · Omnigent maintainers Omnigent 0.9.0 is a major release focused on putting the right model behind every task and polishing the surface you work on all day, from automatic model-and-harness selection to a refreshed web UI. It brings smart routing that sends each task and its subagents to the best model and harness, a redesigned interface built around a Zinc palette and clearer session controls, deeper control over sandboxes and deployments, a first-class Grok Build harness alongside a set of ship-ready example agents, and new Nimble web builtins for pulling structured data straight off the web. ## 1. Smart Routing ![Smart Routing sending a task and its subagents to the best model and harness](https://omnigent.ai/images/releases/0.9.0/smart-routing.gif) Choosing the right model for a task — and for every subagent that task spawns — is a decision you shouldn't have to make by hand on every run. Smart Routing makes it automatic: each task and its subagents are routed to the best model and harness for the job. On Databricks, routing is powered by AI Gateway smart routing. In open source, smart routing uses an LLM classifier to pick the best model. Either way, you turn it on once and let Omnigent match work to the model that fits it. _Learn more in the [Smart Routing](https://omnigent.ai/docs/build/routing) docs._ ## 2. Refreshed web UI The web UI has been redesigned for clarity: a Zinc palette, tightened typography, flush rails, and unified menus and shadows give the whole interface a calmer, more consistent feel. Alongside the visual refresh, session management gets more practical. A session filter menu lets you narrow to All, My, Shared, or Archived sessions, the harness picker is now organized by support tier, and settled turns fold away behind a compact "Worked for Xs" row so long conversations stay readable. _Learn more in the [Web UI](https://omnigent.ai/docs/interact/web-ui) docs._ ## 3. More sandbox and deployment control This release gives you finer control over where and how agents run. Set `sandbox.type: auto` to pick the platform default automatically, and size Boxlite disks with `sandbox.boxlite.disk_size_gb` when a workload needs more room. For team deployments, you can target per-agent Kubernetes runner Pods by label, set a project default base branch for new worktree sessions, and authenticate headless with `omnigent run --profile` against a deployed Databricks app. ## 4. New harnesses and example agents Grok Build (xAI) joins as a first-class ACP harness, so you can drive Omnigent with xAI's coding agent the same way you would any other supported harness. The release also ships a set of ready-to-run example agents: a deep-research agent, a repro-agent that reproduces a bug live and authors an end-to-end test for it, and a resolve-agent that fixes the bug and proves the fix. They're a practical starting point for building your own agents around real workflows. _Learn more in the [Grok Build](https://omnigent.ai/docs/build/harnesses/configuration#grok-build) docs._ Full Changelog: https://github.com/omnigent-ai/omnigent/blob/main/CHANGELOG.md ## What's Next Install or upgrade Omnigent: ```bash uv tool install --python 3.12 omnigent # or: pip install "omnigent" ``` - Star the project and file issues on [GitHub](https://github.com/omnigent-ai/omnigent). - Join the conversation on our [Discord](https://discord.gg/omnigent). - Browse the [docs](https://omnigent.ai/docs) to go deeper. --- ## https://omnigent.ai/releases/0.8.0 # v0.8.0 _Released 2026-08-03_ · 3 min read · Omnigent maintainers Omnigent 0.8.0 is a major release focused on making the platform smarter and more collaborative, from how you pick a model to how you bring in agents. Model choices now come live from your active providers and installed CLIs, the web workspace has been reworked into an editor-style experience, and new guardrail policies catch agents stuck in loops. ## 1. Live model discovery Model lists used to be baked into each release, which meant they drifted out of date the moment a provider shipped something new. Now the choices come straight from your active provider catalog and the CLIs you actually have installed — Kiro, Cursor, and Pi — so the models you see are the models you can run. To keep things stable when an upstream catalog hiccups, Omnigent validates and falls back to a last-known-good catalog, so a provider outage never leaves you without options. You simply pick from the current, live list when you start or switch a session. ## 2. Reworked web workspace and sidebar The web workspace now behaves like a real editor. Files and shells open as tabs, with shells docked in the rail and a full-screen toggle when you want room to work. Markdown previews render Mermaid diagrams inline, and the subagent graph is clickable and zoomable so you can trace what your agents are doing. ## 3. More agents and sign-ins Omnigent 0.8.0 widens the set of agents you can bring in and how you authenticate them. You can sign in to Antigravity with Google OAuth through `agy`, and add `agy` as a Polly sub-agent. Existing OpenClaw and acpx coding agents drop into the ACP picker with `omnigent run --from-openclaw`, and you can launch Claude and Codex children directly in goal mode. ## 4. New guardrail policies Two new policies watch for agents that get stuck. `detect_loop` and `detect_thrashing` catch an agent retrying or failing in a loop and prompt you to step in before it burns time and tokens going nowhere. The GitHub policy also gets safer by default: destructive deletes are now blocked unless you explicitly opt in with `allow_destructive: true`. ## 5. Sharper CLI The command line gets a round of polish. `omni upgrade` now preserves your installed extras and accepts `--extra`, `--target-version`, and `--dry-run` so you can see exactly what an upgrade will do first. `omnigent --help` groups and colorizes harness commands for easier scanning, and `omnigent diagnose` prints a secret-free snapshot of your environment when you need to troubleshoot. Nightly prerelease builds are now cut automatically, so you can ride the latest with `omni upgrade --nightly`. Full Changelog: https://github.com/omnigent-ai/omnigent/blob/main/CHANGELOG.md ## What's Next Install or upgrade Omnigent: ```bash uv tool install --python 3.12 omnigent # or: pip install "omnigent" ``` - Star the project and file issues on [GitHub](https://github.com/omnigent-ai/omnigent). - Join the conversation on our [Discord](https://discord.gg/omnigent). - Browse the [docs](https://omnigent.ai/docs) to go deeper. --- ## https://omnigent.ai/releases/0.7.0 # v0.7.0 _Released 2026-07-27_ · 3 min read · Omnigent maintainers Omnigent 0.7.0 is a major release focused on putting your agents to work on your own terms, from scheduling recurring tasks that run without you to letting Omnigent pick the right harness and model for the job. This release brings first-class Automations and a dedicated `/tasks` page, real Project entities that organize your sessions and pre-fill their defaults, hands-free voice dictation in the composer, and smarter routing that chooses both harness and model straight from your task. ## 1. Automations (Scheduled Tasks) [Video: Creating a scheduled task, running it on demand, and tracking the run](https://omnigent.ai/images/releases/0.7.0/automations.mp4) Some work should just happen on a cadence — a morning digest, a nightly cleanup, a weekly report — without you kicking it off by hand every time. Automations let you set an agent task to run on a recurring schedule and then get out of the way. A new `/tasks` page lets you create, edit, and run recurring agent tasks, complete with model and reasoning-effort selectors and a "Run now" action for when you don't want to wait for the next firing. You get live relative next-run times so you always know what's coming up, plus terminal run tracking with full history available at `GET /v1/scheduled-tasks/{id}/runs`. For chat-only work, tasks no longer require a workspace or a pinned host, so you can automate a conversation as easily as a code job. _Learn more in the [Scheduled Tasks](https://omnigent.ai/docs/build/scheduled-tasks)._ ## 2. First-class Projects [Video: Organizing sessions into first-class projects with per-project defaults](https://omnigent.ai/images/releases/0.7.0/projects.mp4) Projects keep related work grouped instead of scattered across a flat session list. You can create, rename, and delete them right in the sidebar and file your sessions into the project they belong to. Each project can carry its own default session settings — host, working directory, agent, and model — which pre-fill the new-session composer so you're not re-selecting the same options every time you start work in a familiar context. _Learn more in the [Projects](https://omnigent.ai/docs/interact/web-ui#projects)._ ## 3. Voice dictation in the composer [Video: Dictating into the composer with live streaming partials](https://omnigent.ai/images/releases/0.7.0/voice-dictation.mp4) Sometimes it's faster to talk than to type. Optional server-side transcription — installed with `omnigent[dictation]` — lets you dictate straight into the composer, with live streaming partials so you can watch your words appear as you speak. It works in Electron, Firefox, and Chromium, and your audio never leaves your server. Toggle it from anywhere with ⌘⌥V, and if you'd rather not run transcription in-process, the engine can be offloaded to a remote worker. ## 4. Smarter routing [Video: Selecting the Auto smart-routing harness in the composer](https://omnigent.ai/images/releases/0.7.0/smart-routing.mp4) Choosing the right harness and model for each task is friction you shouldn't have to think about. A new "Auto · smart routing" harness option lets the router pick both the harness and the model based on your task itself. Smart routing now activates automatically from your `llm:`/`routing:` config — there's no `OMNIGENT_SMART_ROUTING` env var to set anymore. And when you want to steer things yourself, a composer gear modal lets you tune the model, reasoning effort, and routing mid-chat without leaving the conversation. _Learn more in the [Harnesses](https://omnigent.ai/docs/build/harnesses)._ Full Changelog: https://github.com/omnigent-ai/omnigent/blob/main/CHANGELOG.md ## What's Next Install or upgrade Omnigent: ```bash uv tool install --python 3.12 omnigent # or: pip install "omnigent" ``` - Star the project and file issues on [GitHub](https://github.com/omnigent-ai/omnigent). - Join the conversation on our [Discord](https://discord.gg/omnigent). - Browse the [docs](https://omnigent.ai/docs) to go deeper. --- ## https://omnigent.ai/releases/0.6.0 # v0.6.0 _Released 2026-07-21_ · 3 min read · Omnigent maintainers Omnigent 0.6.0 is a major release focused on meeting you wherever you work, from bringing your existing chats in and reading files richly to driving sessions from Slack and a desktop app on every platform. It lands a guided theme editor for making the UI your own, first-class Slack control complete with inline approvals, and native desktop apps that now run on Windows and Linux alongside macOS. ## 1. Richer file viewing [Video: Preview and review rich files directly in Omnigent](https://omnigent.ai/images/releases/0.6.0/richer-file-viewing.mp4) Reading files inside Omnigent now covers far more than plain source. You get inline PDF preview with review comments, find-in-file across the editor, and rendered previews for markdown and notebooks — so you can review what the agent produced without leaving the session. The diff viewer adds a "Wrap lines" toggle for long lines, and the file panel stays browsable even when the runner is offline, as long as the host is still connected, so you are never locked out of your files mid-session. _Learn more in the [Code viewer and diffs](https://omnigent.ai/docs/interact/web-ui#code-viewer-and-diffs)._ ## 2. Custom themes [Video: Customize Omnigent colors, contrast, and sidebar translucency in the theme editor](https://omnigent.ai/images/releases/0.6.0/custom-themes.mp4) You can now shape Omnigent's look with a guided theme editor. Shared accent, tint, and contrast controls plus a sidebar translucency setting let you tune the whole interface, and there is a new Nord palette to start from — or hit the randomize dice to discover a look you like. The file editor follows your Omnigent theme too, so your chosen palette carries through from the session view into the code you are reading and editing. _Learn more in the [Appearance settings](https://omnigent.ai/docs/interact/web-ui#appearance-settings)._ ## 3. Bring your existing work in Starting fresh in a new tool usually means leaving your history behind. Now you can import your Claude Code and Codex chats directly with `omni import`, bringing up to 50 conversations in at once. Imported and new sessions also get automatic first-turn titles, so your list stays readable without any manual naming. _Learn more in the [Import existing chats](https://omnigent.ai/docs/use/coding-agents#import-existing-chats)._ ## 4. Navigation bar in transcript [Video: Navigate through a long session transcript using the transcript navigation bar](https://omnigent.ai/images/releases/0.6.0/message-navigation.mp4) Long transcripts are now easy to move through. A navigation bar in the session transcript lets you jump around your history with a click, so you can find an earlier turn or result without endless scrolling. ## 5. Slack integration ![Start and manage an Omnigent session from Slack](https://omnigent.ai/images/releases/0.6.0/slack-integration.png) You can now kick off and drive Omnigent sessions straight from Slack. Beyond starting work, you can approve tool calls and answer the agent's questions inline in the channel, so you can keep a session moving from wherever your team already talks. _Learn more in the [Slack](https://omnigent.ai/docs/interact/slack)._ ## 6. Omnigent desktop app on Windows and Linux The Omnigent desktop app is no longer macOS-only. Windows and Linux builds are now available, so you can run the native app on your platform of choice. Download the build for your system from the website. _Learn more in the [Install](https://omnigent.ai/docs/interact/desktop#install)._ Full Changelog: https://github.com/omnigent-ai/omnigent/blob/main/CHANGELOG.md ## What's Next Install or upgrade Omnigent: ```bash uv tool install --python 3.12 omnigent # or: pip install "omnigent" ``` - Star the project and file issues on [GitHub](https://github.com/omnigent-ai/omnigent). - Join the conversation on our [Discord](https://discord.gg/omnigent). - Browse the [docs](https://omnigent.ai/docs) to go deeper. --- ## https://omnigent.ai/releases/0.5.1 # v0.5.1 _Released 2026-07-10_ ## Bug fixes - 🖥️ On older desktop builds without the embedded browser, the Browser tab is now hidden instead of showing a dead tab that does nothing ([#2393](https://github.com/omnigent-ai/omnigent/pull/2393)) ### 💜 Thanks to our community This release was shaped by the people who filed issues, opened PRs, and talked through feature requests with us on our Discord! Thank you for building omnigent with us, keep the bug reports, ideas and contributions coming :) Full Changelog: https://github.com/omnigent-ai/omnigent/blob/main/CHANGELOG.md --- ## https://omnigent.ai/releases/0.5.0 # v0.5.0 _Released 2026-07-10_ ## Major new features - 📱 **Omnigent for iOS — now on the App Store** — the native iOS app is live on the [App Store](https://apps.apple.com/us/app/omnigent/id6783102694); Android is coming soon ([#2262](https://github.com/omnigent-ai/omnigent/pull/2262), [#2263](https://github.com/omnigent-ai/omnigent/pull/2263), [#2282](https://github.com/omnigent-ai/omnigent/pull/2282), [#2179](https://github.com/omnigent-ai/omnigent/pull/2179)) - 🧩 **Generic ACP harness** — connect Omnigent to any ACP-compatible agent, bridged to Omnigent's own tools over an MCP relay ([#2152](https://github.com/omnigent-ai/omnigent/pull/2152)) - 🔎 **Command palette & richer search** — a global ⌘/Ctrl+K palette (also wired to the sidebar "Search") jumps across sessions, and session search now shows a highlighted matched-content preview so you can see why a session matched ([#1386](https://github.com/omnigent-ai/omnigent/pull/1386), [#2086](https://github.com/omnigent-ai/omnigent/pull/2086), [#2162](https://github.com/omnigent-ai/omnigent/pull/2162)) - 🎨 **Appearance & customization settings** — pick a color theme (Omnigent, Dracula, GitHub, Catppuccin, Gruvbox), a terminal theme, and independent UI and code editor/terminal font size + family ([#2225](https://github.com/omnigent-ai/omnigent/pull/2225), [#2147](https://github.com/omnigent-ai/omnigent/pull/2147), [#2154](https://github.com/omnigent-ai/omnigent/pull/2154), [#2135](https://github.com/omnigent-ai/omnigent/pull/2135), [#2040](https://github.com/omnigent-ai/omnigent/pull/2040), [#2047](https://github.com/omnigent-ai/omnigent/pull/2047)) - 💬 **Message steering & queuing** — line up several messages while a turn runs, then edit, reorder, delete, or steer them; queued messages auto-flush on idle, in the background, and across sessions (with attachments) ([#2008](https://github.com/omnigent-ai/omnigent/pull/2008), [#2010](https://github.com/omnigent-ai/omnigent/pull/2010), [#2019](https://github.com/omnigent-ai/omnigent/pull/2019), [#2022](https://github.com/omnigent-ai/omnigent/pull/2022), [#2025](https://github.com/omnigent-ai/omnigent/pull/2025), [#2029](https://github.com/omnigent-ai/omnigent/pull/2029), [#2078](https://github.com/omnigent-ai/omnigent/pull/2078)) - ☸️ **Kubernetes deployment** — a new official `omnigent-server-kubernetes` image ships the sandbox provider SDK (no custom build), and runner Pods can now schedule on arm64 via `node_selector` ([#2124](https://github.com/omnigent-ai/omnigent/pull/2124), [#2123](https://github.com/omnigent-ai/omnigent/pull/2123)) - 🌿 **Start sessions in git worktrees** — pick an existing worktree or generate a fresh branch name right from the new-session composer, see the branch in the sidebar, and delete the worktree + branch on session delete; set a default base branch in Settings › Git ([#2088](https://github.com/omnigent-ai/omnigent/pull/2088), [#2094](https://github.com/omnigent-ai/omnigent/pull/2094), [#2098](https://github.com/omnigent-ai/omnigent/pull/2098), [#2267](https://github.com/omnigent-ai/omnigent/pull/2267)) - 🌐 **Embedded browser** — the Omnigent desktop app now ships a built-in browser pane your agent can drive with framework-owned tools (`browser_navigate`, `browser_click`, `browser_type`, `browser_snapshot`, `browser_screenshot`) — it opens a page, reads it, and acts on it without leaving the workspace ([#2248](https://github.com/omnigent-ai/omnigent/pull/2248)) - 🖥️ **Better web terminal** — tmux control-mode (CC) is now the default transport, so the browser owns scrollback and text selection natively in the terminal view (no more fighting tmux for the viewport or mouse), with output coalesced into fewer WebSocket frames for a snappier feel ([#1970](https://github.com/omnigent-ai/omnigent/pull/1970), [#1972](https://github.com/omnigent-ai/omnigent/pull/1972)) ## Bug fixes - 🖥️ **claude-native reliability** — resumed sessions no longer crash on compaction or `TaskOutput` history, in-pane `/model` switches no longer stick on "Stop", and mid-turn messages no longer raise a false "terminal did not become ready" error ([#1957](https://github.com/omnigent-ai/omnigent/pull/1957), [#2293](https://github.com/omnigent-ai/omnigent/pull/2293), [#2082](https://github.com/omnigent-ai/omnigent/pull/2082), [#2001](https://github.com/omnigent-ai/omnigent/pull/2001), [#2089](https://github.com/omnigent-ai/omnigent/pull/2089)) - 🧭 **Smart routing & compaction** — intelligent routing now correctly routes Claude sessions and overrides an orchestrator-specified model when the parent toggle is on, and `/compact` on a pinned Anthropic model no longer 500s ([#2136](https://github.com/omnigent-ai/omnigent/pull/2136), [#2291](https://github.com/omnigent-ai/omnigent/pull/2291), [#2043](https://github.com/omnigent-ai/omnigent/pull/2043)) - 🚀 **Launch & gateway auth** — harness workers now start when Omnigent is launched from a macOS/Linux GUI client (stripped PATH), and managed claude-native sessions against an Anthropic-compatible gateway pass through the model instead of stalling on the API-key menu ([#1935](https://github.com/omnigent-ai/omnigent/pull/1935), [#2371](https://github.com/omnigent-ai/omnigent/pull/2371)) - ✅ **Approvals & policies** — inbox approvals now resume the gated tool call, and default policies created via the API now take effect on sessions ([#2142](https://github.com/omnigent-ai/omnigent/pull/2142), [#2333](https://github.com/omnigent-ai/omnigent/pull/2333)) - ⏱️ **Idle-session stability** — messaging a long-idle session no longer risks the new turn being killed mid-flight by the idle reaper ([#1834](https://github.com/omnigent-ai/omnigent/pull/1834)) ### 💜 Thanks to our community This release was shaped by the people who filed issues, opened PRs, and talked through feature requests with us on our Discord! Thank you for building omnigent with us, keep the bug reports, ideas and contributions coming :) Full Changelog: https://github.com/omnigent-ai/omnigent/blob/main/CHANGELOG.md --- ## https://omnigent.ai/releases/0.4.0 # v0.4.0 _Released 2026-07-03_ ## Install ```bash uv tool install --force "omnigent==0.4.0" ``` 0.4.0 turns harnesses into a real plugin platform — bring your own coding agent as an installable package — and matures the whole native-harness fleet with parity, cost, and polish across the board. ## Major new features - 🤹 **Polly multi-agent orchestration:** the polly example orchestrator now fans work out across more coding sub-agents — cursor, hermes, and opencode join the roster — so a single run can delegate to the right agent for each task ([#1844](https://github.com/omnigent-ai/omnigent/pull/1844), [#1776](https://github.com/omnigent-ai/omnigent/pull/1776)) - 🔌 **Harness plugin SDK:** bring your own coding agent — any harness can now ship as an installable Python package (via the `omnigent.community.harness` entry point), no fork or core changes required. A declarative capability model + a `/v1/harnesses` catalog let the server and UI discover what each harness supports ([#1756](https://github.com/omnigent-ai/omnigent/pull/1756), [#1847](https://github.com/omnigent-ai/omnigent/pull/1847), [#1894](https://github.com/omnigent-ai/omnigent/pull/1894)) - 🪢 **Native-harness maturity across the fleet:** - **kiro-native** reaches full parity: Omnigent tools inside kiro sessions, a launch-time model picker, TUI approvals surfaced as chat cards, working interrupt / Stop, and credit usage tracked as session cost ([#1709](https://github.com/omnigent-ai/omnigent/pull/1709), [#1706](https://github.com/omnigent-ai/omnigent/pull/1706), [#1715](https://github.com/omnigent-ai/omnigent/pull/1715), [#1293](https://github.com/omnigent-ai/omnigent/pull/1293), [#1531](https://github.com/omnigent-ai/omnigent/pull/1531), [#1699](https://github.com/omnigent-ai/omnigent/pull/1699)) - **claude-native** renders live tool-call cards in the web chat, shows background-shell status, and takes a pluggable launch command ([#1499](https://github.com/omnigent-ai/omnigent/pull/1499), [#1578](https://github.com/omnigent-ai/omnigent/pull/1578), [#1476](https://github.com/omnigent-ai/omnigent/pull/1476)) - **qwen-native** gains Omnigent tools in the TUI and carries conversation history across fork / switch-agent ([#1559](https://github.com/omnigent-ai/omnigent/pull/1559), [#1576](https://github.com/omnigent-ai/omnigent/pull/1576)) - **GitHub Copilot** reports authoritative AI-credit cost, forwards reasoning effort, emits compaction-complete events, and gates its native tools through policy ([#1486](https://github.com/omnigent-ai/omnigent/pull/1486), [#1503](https://github.com/omnigent-ai/omnigent/pull/1503), [#1505](https://github.com/omnigent-ai/omnigent/pull/1505), [#1511](https://github.com/omnigent-ai/omnigent/pull/1511)) - 🧩 **VS Code extension (coming very soon):** open and drive your running Omnigent server without leaving the editor — landing shortly, not on the Marketplace just yet ([#1288](https://github.com/omnigent-ai/omnigent/pull/1288), [#1855](https://github.com/omnigent-ai/omnigent/pull/1855)) - 🛡️ **Better control & elicitation:** clicking **Decline** now cleanly cancels the turn instead of nudging the model to retry, a new `intent_gate` keeps an agent on its original task, and sub-agents can carry their own cost budgets ([#1839](https://github.com/omnigent-ai/omnigent/pull/1839), [#1777](https://github.com/omnigent-ai/omnigent/pull/1777), [#1538](https://github.com/omnigent-ai/omnigent/pull/1538)) - 💻 **Web & CLI polish:** unread/seen state syncs across devices, compaction shows a progress bar instead of a silent pause, a new `!` shell passthrough folds command output into your next turn, half-typed drafts survive navigation, the file viewer gains image zoom + inline comments, and the app installs as an offline-capable PWA ([#1679](https://github.com/omnigent-ai/omnigent/pull/1679), [#1304](https://github.com/omnigent-ai/omnigent/pull/1304), [#1524](https://github.com/omnigent-ai/omnigent/pull/1524), [#1659](https://github.com/omnigent-ai/omnigent/pull/1659), [#1775](https://github.com/omnigent-ai/omnigent/pull/1775), [#116](https://github.com/omnigent-ai/omnigent/pull/116)) - 🔎 **Zero-setup web search:** `web_search` works out of the box with a keyless DuckDuckGo backend; add Tavily for grounded search on any model ([#918](https://github.com/omnigent-ai/omnigent/pull/918), [#1339](https://github.com/omnigent-ai/omnigent/pull/1339)) ## Bug fixes - **Connections hold under load** — a ws-tunnel keepalive fix ends mid-session disconnects ([#1727](https://github.com/omnigent-ai/omnigent/pull/1727)). - **Sub-agent results reliably reach the parent session** now, instead of getting lost ([#1770](https://github.com/omnigent-ai/omnigent/pull/1770)). - **pi-native recovers on long sessions** — tool calls no longer start failing about an hour in ([#1621](https://github.com/omnigent-ai/omnigent/pull/1621)). - **Native harnesses survive sandbox restarts** — codex/claude-native restore compaction + transcript on resume ([#1535](https://github.com/omnigent-ai/omnigent/pull/1535)). - **No more zombie processes or runaway terminal panes** — orphaned tool/harness processes are reaped and idle panes self-heal ([#1812](https://github.com/omnigent-ai/omnigent/pull/1812), [#1624](https://github.com/omnigent-ai/omnigent/pull/1624)). - **Accurate costs** — fixed a lost-update race in session usage and stopped double-billing Cursor cache tokens ([#1664](https://github.com/omnigent-ai/omnigent/pull/1664), [#1802](https://github.com/omnigent-ai/omnigent/pull/1802)). ...and more! --- ## 💜 Thanks to our community This release was shaped by the people who filed issues, opened PRs, and talked through feature requests with us on our Discord! Thank you for building Omnigent with us, keep the bug reports, ideas and contributions coming :) **Full Changelog:** https://github.com/omnigent-ai/omnigent/compare/v0.3.0...v0.4.0 --- ## https://omnigent.ai/releases/0.3.0 # v0.3.0 _Released 2026-06-27_ ## Major new features - 🤖 **Seven new agent harnesses:** Hermes (SDK + native TUI), GitHub Copilot (SDK), OpenCode, Goose, Qwen Code, Kiro, and Kimi Code; plus Antigravity promoted to a full SDK + native agy CLI harness ([#1132](https://github.com/omnigent-ai/omnigent/pull/1132), [#330](https://github.com/omnigent-ai/omnigent/pull/330), [#576](https://github.com/omnigent-ai/omnigent/pull/576), [#955](https://github.com/omnigent-ai/omnigent/pull/955), [#1020](https://github.com/omnigent-ai/omnigent/pull/1020), [#899](https://github.com/omnigent-ai/omnigent/pull/899), [#521](https://github.com/omnigent-ai/omnigent/pull/521), [#892](https://github.com/omnigent-ai/omnigent/pull/892)) - 🪢 **Native-harness parity across the fleet:** compaction, cost/token tracking, resume, true fork with history, in-session model switching, and tool-approval / AskQuestion web cards, for cursor, codex, qwen, pi, hermes, goose, and opencode ([#1303](https://github.com/omnigent-ai/omnigent/pull/1303), [#1331](https://github.com/omnigent-ai/omnigent/pull/1331), [#1271](https://github.com/omnigent-ai/omnigent/pull/1271), [#1260](https://github.com/omnigent-ai/omnigent/pull/1260)) - 🖥️ **Omnigent Desktop:** now manages the server + runner automatically ([#1437](https://github.com/omnigent-ai/omnigent/pull/1437), [#1445](https://github.com/omnigent-ai/omnigent/pull/1445)) - 🗂️ **Projects workspace:** group sessions into Projects and drag between them, plus a Settings surface and per-harness run-mode memory ([#1341](https://github.com/omnigent-ai/omnigent/pull/1341), [#1432](https://github.com/omnigent-ai/omnigent/pull/1432), [#1110](https://github.com/omnigent-ai/omnigent/pull/1110)) - 📦 **More deploy & sandbox targets:** host on Databricks Apps + Lakebase Postgres ([#956](https://github.com/omnigent-ai/omnigent/pull/956)), AWS Bedrock provider ([#901](https://github.com/omnigent-ai/omnigent/pull/901)), on-demand Kubernetes runner Pod sandbox ([#881](https://github.com/omnigent-ai/omnigent/pull/881)), and a boxlite managed host ([#102](https://github.com/omnigent-ai/omnigent/pull/102)) - 🪟 **Native Windows support** (only core features) ([#1236](https://github.com/omnigent-ai/omnigent/pull/1236)) - 🕵️ **Custom Agent Creation UI** ([#1098](https://github.com/omnigent-ai/omnigent/pull/1098)) - 🔌 In-app MCP management from Agent Info ([#1093](https://github.com/omnigent-ai/omnigent/pull/1093)), sys_session_share agent-facing session sharing ([#985](https://github.com/omnigent-ai/omnigent/pull/985)), and web UX polish — shortcuts overlay, pinned-session hotkeys, image lightbox ([#833](https://github.com/omnigent-ai/omnigent/pull/833), [#967](https://github.com/omnigent-ai/omnigent/pull/967), [#1334](https://github.com/omnigent-ai/omnigent/pull/1334)) ## Bug fixes & hardening - Authoritative context-window resolution + compaction-failure surfacing + /context meter; compaction ownership moved runner→harness, ending compact-every-message ([#1121](https://github.com/omnigent-ai/omnigent/pull/1121), [#1169](https://github.com/omnigent-ai/omnigent/pull/1169), [#1082](https://github.com/omnigent-ai/omnigent/pull/1082)). - Native harnesses no longer fail silently — codex/antigravity surface real turn/model/auth errors instead of empty/false success ([#1108](https://github.com/omnigent-ai/omnigent/pull/1108), [#1172](https://github.com/omnigent-ai/omnigent/pull/1172)). - Policy checks fail closed when turn context is missing ([#1078](https://github.com/omnigent-ai/omnigent/pull/1078)). - Parallel sub-agents — serialized continuation turn-starts fix the 204 race ([#996](https://github.com/omnigent-ai/omnigent/pull/996)). - claude-native stability — tmux server survives inner-CLI exit ([#849](https://github.com/omnigent-ai/omnigent/pull/849)). ...and more! Full Changelog: https://github.com/omnigent-ai/omnigent/compare/v0.2.0...v0.3.0rc1 --- ## 💜 Thanks to our community This release was shaped by the people who filed issues, opened PRs, and talked through feature requests with us on our Discord! Thank you for building Omnigent with us, keep the bug reports, ideas and contributions coming :) --- ## https://omnigent.ai/releases/0.2.0 # v0.2.0 _Released 2026-06-19_ ## Install ```bash uv tool install --force "omnigent==0.2.0" ``` 0.2.0 turns omnigent into a multi-vendor agent platform with pluggable agent SDKs, a much wider fleet of sandbox & deploy targets, and hardened security underneath. ## Major new features - **🤖 More agent harnesses & SDKs** - run agents on Google Antigravity (Gemini 3 Pro, also Claude/GPT-OSS) ([#194](https://github.com/omnigent-ai/omnigent/pull/194)) - Cursor via a first-party Python SDK _and_ a native `cursor-agent` (ACP) harness with cost tracking ([#203](https://github.com/omnigent-ai/omnigent/pull/203), [#551](https://github.com/omnigent-ai/omnigent/pull/551), [#635](https://github.com/omnigent-ai/omnigent/pull/635)) - Native `/compact` for Codex and Pi login folded into `omnigent setup` ([#285](https://github.com/omnigent-ai/omnigent/pull/285), [#207](https://github.com/omnigent-ai/omnigent/pull/207)) - **📦 More sandboxes & deploy targets** - new sandbox providers NVIDIA OpenShell, E2B, CoreWeave, and Podman ([#227](https://github.com/omnigent-ai/omnigent/pull/227), [#565](https://github.com/omnigent-ai/omnigent/pull/565), [#302](https://github.com/omnigent-ai/omnigent/pull/302), [#401](https://github.com/omnigent-ai/omnigent/pull/401)) - deploy to Cloudflare Containers (D1 + R2) with an S3 artifact store, plus a UBI9 image for RHEL/OpenShift ([#651](https://github.com/omnigent-ai/omnigent/pull/651), [#167](https://github.com/omnigent-ai/omnigent/pull/167)) - **⬆️ `omni upgrade`**: self-upgrade the CLI to the latest release, with an out-of-date notice on startup ([#188](https://github.com/omnigent-ai/omnigent/pull/188)). - **🔒 Secretless sandbox egress**: `credential_proxy` injects bearer/basic auth for allow-listed hosts so secrets never enter the sandbox ([#236](https://github.com/omnigent-ai/omnigent/pull/236)) - **📊 MLflow tracing** end-to-end through `omnigent run` ([#638](https://github.com/omnigent-ai/omnigent/pull/638)) ## Bug fixes - **`omni upgrade` is version-aware** — upgrades to the right release instead of looping against a stale version ([#726](https://github.com/omnigent-ai/omnigent/pull/726)). - **`omnigent run` exits cleanly on an INPUT-phase policy denial** instead of hanging ([#599](https://github.com/omnigent-ai/omnigent/pull/599)). - **The REPL no longer crashes** when the daemon returns a non-JSON status response ([#660](https://github.com/omnigent-ai/omnigent/pull/660)). **Full changelog:** https://github.com/omnigent-ai/omnigent/compare/v0.1.1...v0.2.0 --- ## 💜 Thanks to our community This release was shaped by the people who filed issues, opened PRs, and talked through feature requests with us on our discord! Thank you for building Omnigent with us, keep the bug reports, ideas and contributions coming :)