diff --git a/.icons/omnigent.svg b/.icons/omnigent.svg
new file mode 100644
index 000000000..b70b60659
--- /dev/null
+++ b/.icons/omnigent.svg
@@ -0,0 +1,44 @@
+
diff --git a/registry/coder-labs/modules/omnigent/README.md b/registry/coder-labs/modules/omnigent/README.md
new file mode 100644
index 000000000..710189b90
--- /dev/null
+++ b/registry/coder-labs/modules/omnigent/README.md
@@ -0,0 +1,144 @@
+---
+display_name: Omnigent
+icon: ../../../../.icons/omnigent.svg
+description: Run a private Omnigent multi-agent coding server in your workspace.
+verified: false
+tags: [agent, omnigent, ai, multi-agent]
+---
+
+# Omnigent
+
+Run a private [Omnigent](https://github.com/omnigent-dev) multi-agent coding orchestrator server inside your Coder workspace. Each workspace gets its own isolated Omnigent instance with a stable, derived admin password — no shared credentials, no manual password management.
+
+The module installs Omnigent via the [official install script](https://omnigent.ai/install.sh), starts the server on a configurable port, waits for the health endpoint, and registers the local workspace as a host. The admin password is derived from the workspace ID at runtime and never stored in Terraform state.
+
+```tf
+module "omnigent" {
+ source = "registry.coder.com/coder-labs/omnigent/coder"
+ version = "1.0.0"
+ agent_id = coder_agent.main.id
+}
+```
+
+## Examples
+
+### With a custom port
+
+```tf
+module "omnigent" {
+ source = "registry.coder.com/coder-labs/omnigent/coder"
+ version = "1.0.0"
+ agent_id = coder_agent.main.id
+ port = 7878
+}
+```
+
+### With AI tools (Omnigent + Claude Code + Codex)
+
+Compose Omnigent alongside other AI agent modules to create a full multi-agent workspace. This example authenticates Claude Code and Codex through Coder AI Gateway.
+
+```tf
+module "codex" {
+ source = "registry.coder.com/coder-labs/codex/coder"
+ version = "5.0.0"
+
+ agent_id = coder_agent.main.id
+ enable_ai_gateway = true
+}
+
+module "claude_code" {
+ source = "registry.coder.com/coder/claude-code/coder"
+ version = ">= 4.0.0"
+
+ agent_id = coder_agent.main.id
+ enable_ai_gateway = true
+}
+
+module "omnigent" {
+ source = "registry.coder.com/coder-labs/omnigent/coder"
+ version = "1.0.0"
+
+ agent_id = coder_agent.main.id
+}
+```
+
+### Policies (server-wide)
+
+```tf
+module "omnigent" {
+ source = "registry.coder.com/coder-labs/omnigent/coder"
+ version = "1.0.0"
+ agent_id = coder_agent.main.id
+
+ server_config = <<-YAML
+ policies:
+ cap_tool_calls:
+ type: function
+ handler: omnigent.policies.builtins.safety.max_tool_calls_per_session
+ factory_params:
+ limit: 50
+ require_approval:
+ type: function
+ handler: omnigent.policies.builtins.safety.ask_on_os_tools
+ YAML
+}
+```
+
+### Custom agents
+
+```tf
+module "omnigent" {
+ source = "registry.coder.com/coder-labs/omnigent/coder"
+ version = "1.0.0"
+ agent_id = coder_agent.main.id
+
+ agents = [
+ {
+ name = "reviewer"
+ content = <<-YAML
+ name: reviewer
+ instructions: You are an expert code reviewer. Focus on correctness, security, and clarity.
+ executor:
+ harness: claude-sdk
+ model: claude-sonnet-4-5
+ YAML
+ }
+ ]
+}
+```
+
+### Bring-your-own config file
+
+```tf
+module "omnigent" {
+ source = "registry.coder.com/coder-labs/omnigent/coder"
+ version = "1.0.0"
+ agent_id = coder_agent.main.id
+ server_config_path = "/home/coder/.omnigent/server_config.yaml"
+}
+```
+
+## Troubleshooting
+
+Script logs are written to `~/.coder-modules/coder-labs/omnigent/logs/`. If the Omnigent app shows as unhealthy or the server fails to start, check:
+
+```bash
+cat ~/.coder-modules/coder-labs/omnigent/logs/server.log
+cat ~/.coder-modules/coder-labs/omnigent/logs/start.log
+cat ~/.coder-modules/coder-labs/omnigent/logs/install.log
+cat ~/.coder-modules/coder-labs/omnigent/logs/host.log
+```
+
+The health endpoint is available at `http://localhost:/health`. You can check it directly:
+
+```bash
+curl -sf http://localhost:6767/health && echo "healthy" || echo "not ready"
+```
+
+### Finding the admin password
+
+The admin password is derived from the workspace ID at runtime. To retrieve it inside the workspace:
+
+```bash
+echo -n "$CODER_WORKSPACE_ID" | tr -d '-' | cut -c1-16
+```
diff --git a/registry/coder-labs/modules/omnigent/main.tf b/registry/coder-labs/modules/omnigent/main.tf
new file mode 100644
index 000000000..7b758a3d1
--- /dev/null
+++ b/registry/coder-labs/modules/omnigent/main.tf
@@ -0,0 +1,165 @@
+terraform {
+ required_version = ">= 1.9"
+
+ required_providers {
+ coder = {
+ source = "coder/coder"
+ version = ">= 2.13"
+ }
+ }
+}
+
+variable "agent_id" {
+ description = "The ID of a Coder agent."
+ type = string
+}
+
+variable "icon" {
+ description = "Icon for Omnigent scripts and app."
+ type = string
+ default = "../../../../.icons/omnigent.svg"
+}
+
+variable "port" {
+ description = "Port the Omnigent server listens on inside the workspace."
+ type = number
+ default = 6767
+ validation {
+ condition = var.port > 1024 && var.port < 65536
+ error_message = "port must be between 1025 and 65535."
+ }
+}
+
+variable "omnigent_version" {
+ description = "Omnigent version to install. 'latest' installs the newest release."
+ type = string
+ default = "latest"
+}
+
+variable "share" {
+ description = "Coder app share level."
+ type = string
+ default = "owner"
+ validation {
+ condition = contains(["owner", "authenticated", "public"], var.share)
+ error_message = "share must be one of: owner, authenticated, public."
+ }
+}
+
+variable "order" {
+ description = "Order for the Omnigent app in the Coder UI."
+ type = number
+ default = null
+}
+
+variable "server_config" {
+ description = "Inline server_config.yaml content for the Omnigent server. Supports policies, policy_modules, admins, and allowed_domains keys. When set, written to the module directory and passed as -c to the server. Mutually exclusive with server_config_path."
+ type = string
+ default = null
+ validation {
+ condition = !(var.server_config != null && var.server_config_path != null)
+ error_message = "Only one of server_config or server_config_path may be set."
+ }
+}
+
+variable "server_config_path" {
+ description = "Path to an existing server_config.yaml in the workspace. When set, passed directly as -c to the server; no config file is written by this module. Mutually exclusive with server_config."
+ type = string
+ default = null
+}
+
+variable "agents" {
+ description = "Custom agent YAML definitions to pre-register at server startup. Each entry is written to the module directory and passed as --agent flags."
+ type = list(object({
+ name = string
+ content = string
+ }))
+ default = []
+}
+
+variable "pre_install_script" {
+ description = "Custom script to run before installing Omnigent."
+ type = string
+ default = null
+}
+
+variable "post_install_script" {
+ description = "Custom script to run after installing Omnigent."
+ type = string
+ default = null
+}
+
+locals {
+ module_dir = "$HOME/.coder-modules/coder-labs/omnigent"
+ server_config_file = "${local.module_dir}/config/server.yaml"
+ agents_dir = "${local.module_dir}/agents"
+
+ effective_server_config_path = (
+ var.server_config_path != null ? var.server_config_path :
+ var.server_config != null ? local.server_config_file :
+ null
+ )
+
+ install_script = templatefile("${path.module}/scripts/install.sh.tftpl", {
+ ARG_OMNIGENT_VERSION_B64 = var.omnigent_version != "latest" ? base64encode(var.omnigent_version) : ""
+ ARG_PORT = tostring(var.port)
+ ARG_WRITE_SERVER_CONFIG = tostring(var.server_config != null)
+ ARG_SERVER_CONFIG_B64 = var.server_config != null ? base64encode(var.server_config) : ""
+ ARG_SERVER_CONFIG_FILE = local.server_config_file
+ ARG_SERVER_CONFIG_DIR = "${local.module_dir}/config"
+ ARG_AGENTS_B64 = length(var.agents) > 0 ? base64encode(join("\n", [for a in var.agents : "${a.name}\t${base64encode(a.content)}"])) : ""
+ ARG_AGENTS_DIR = local.agents_dir
+ })
+
+ start_script = templatefile("${path.module}/scripts/start.sh.tftpl", {
+ ARG_PORT = tostring(var.port)
+ ARG_EFFECTIVE_SERVER_CONFIG_PATH = local.effective_server_config_path != null ? local.effective_server_config_path : ""
+ ARG_AGENTS_DIR = local.agents_dir
+ })
+}
+
+module "coder_utils" {
+ source = "registry.coder.com/coder/coder-utils/coder"
+ version = "0.0.1"
+
+ agent_id = var.agent_id
+ module_directory = local.module_dir
+ display_name_prefix = "Omnigent"
+ icon = var.icon
+ pre_install_script = var.pre_install_script
+ post_install_script = var.post_install_script
+ install_script = local.install_script
+ start_script = local.start_script
+}
+
+resource "coder_app" "omnigent" {
+ agent_id = var.agent_id
+ slug = "omnigent"
+ display_name = "Omnigent"
+ url = "http://localhost:${var.port}"
+ icon = var.icon
+ subdomain = true
+ share = var.share
+ order = var.order
+
+ healthcheck {
+ url = "http://localhost:${var.port}/health"
+ interval = 15
+ threshold = 3
+ }
+}
+
+output "scripts" {
+ description = "Ordered list of coder exp sync names produced by this module, in run order."
+ value = module.coder_utils.scripts
+}
+
+output "port" {
+ description = "Port the Omnigent server is listening on."
+ value = var.port
+}
+
+output "server_config_path" {
+ description = "Effective path to the server config file, or empty string if no config is used."
+ value = local.effective_server_config_path != null ? local.effective_server_config_path : ""
+}
diff --git a/registry/coder-labs/modules/omnigent/main.tftest.hcl b/registry/coder-labs/modules/omnigent/main.tftest.hcl
new file mode 100644
index 000000000..341703165
--- /dev/null
+++ b/registry/coder-labs/modules/omnigent/main.tftest.hcl
@@ -0,0 +1,239 @@
+run "test_defaults" {
+ command = plan
+
+ variables {
+ agent_id = "test-agent"
+ }
+
+ assert {
+ condition = var.port == 6767
+ error_message = "port should default to 6767"
+ }
+
+ assert {
+ condition = var.share == "owner"
+ error_message = "share should default to owner"
+ }
+
+ assert {
+ condition = var.omnigent_version == "latest"
+ error_message = "omnigent_version should default to latest"
+ }
+
+ assert {
+ condition = coder_app.omnigent.url == "http://localhost:6767"
+ error_message = "coder_app url should use default port 6767"
+ }
+
+ assert {
+ condition = coder_app.omnigent.share == "owner"
+ error_message = "coder_app share should default to owner"
+ }
+}
+
+run "test_custom_port" {
+ command = plan
+
+ variables {
+ agent_id = "test-agent"
+ port = 8080
+ }
+
+ assert {
+ condition = var.port == 8080
+ error_message = "port should be set to 8080"
+ }
+
+ assert {
+ condition = coder_app.omnigent.url == "http://localhost:8080"
+ error_message = "coder_app url should use custom port 8080"
+ }
+}
+
+run "test_custom_share" {
+ command = plan
+
+ variables {
+ agent_id = "test-agent"
+ share = "authenticated"
+ }
+
+ assert {
+ condition = var.share == "authenticated"
+ error_message = "share should be set to authenticated"
+ }
+
+ assert {
+ condition = coder_app.omnigent.share == "authenticated"
+ error_message = "coder_app share should be authenticated"
+ }
+}
+
+run "test_custom_version" {
+ command = plan
+
+ variables {
+ agent_id = "test-agent"
+ omnigent_version = "0.1.0"
+ }
+
+ assert {
+ condition = var.omnigent_version == "0.1.0"
+ error_message = "omnigent_version should be set to 0.1.0"
+ }
+}
+
+run "test_scripts_output" {
+ command = plan
+
+ variables {
+ agent_id = "test-agent"
+ }
+
+ assert {
+ condition = length(output.scripts) > 0
+ error_message = "scripts output should be non-empty"
+ }
+}
+
+run "test_install_script_installs_uv" {
+ command = plan
+
+ variables {
+ agent_id = "test-agent"
+ }
+
+ assert {
+ condition = strcontains(local.install_script, "https://astral.sh/uv/install.sh")
+ error_message = "install script should install uv when it is missing"
+ }
+
+ assert {
+ condition = strcontains(local.install_script, "command -v uv")
+ error_message = "install script should check whether uv is available"
+ }
+}
+
+run "test_start_script_backgrounds_host" {
+ command = plan
+
+ variables {
+ agent_id = "test-agent"
+ }
+
+ assert {
+ condition = strcontains(local.start_script, "nohup omnigent host")
+ error_message = "start script should run the Omnigent host in the background"
+ }
+
+ assert {
+ condition = strcontains(local.start_script, "host.log")
+ error_message = "start script should write Omnigent host logs to host.log"
+ }
+}
+
+run "test_port_output" {
+ command = plan
+
+ variables {
+ agent_id = "test-agent"
+ port = 7777
+ }
+
+ assert {
+ condition = output.port == 7777
+ error_message = "port output should match the configured port"
+ }
+}
+
+run "test_invalid_port_low" {
+ command = plan
+
+ variables {
+ agent_id = "test-agent"
+ port = 80
+ }
+
+ expect_failures = [var.port]
+}
+
+run "test_invalid_port_high" {
+ command = plan
+
+ variables {
+ agent_id = "test-agent"
+ port = 65536
+ }
+
+ expect_failures = [var.port]
+}
+
+run "test_invalid_share" {
+ command = plan
+
+ variables {
+ agent_id = "test-agent"
+ share = "invalid"
+ }
+
+ expect_failures = [var.share]
+}
+
+run "test_server_config" {
+ command = plan
+
+ variables {
+ agent_id = "test-agent"
+ server_config = "policies: {}"
+ }
+
+ assert {
+ condition = var.server_config == "policies: {}"
+ error_message = "server_config should be set"
+ }
+}
+
+run "test_server_config_path" {
+ command = plan
+
+ variables {
+ agent_id = "test-agent"
+ server_config_path = "/home/coder/.omnigent/server.yaml"
+ }
+
+ assert {
+ condition = output.server_config_path == "/home/coder/.omnigent/server.yaml"
+ error_message = "server_config_path output should match the provided path"
+ }
+}
+
+run "test_server_config_mutual_exclusion" {
+ command = plan
+
+ variables {
+ agent_id = "test-agent"
+ server_config = "policies: {}"
+ server_config_path = "/home/coder/.omnigent/server.yaml"
+ }
+
+ expect_failures = [var.server_config]
+}
+
+run "test_agents" {
+ command = plan
+
+ variables {
+ agent_id = "test-agent"
+ agents = [
+ {
+ name = "reviewer"
+ content = "name: reviewer\ninstructions: You are a reviewer."
+ }
+ ]
+ }
+
+ assert {
+ condition = length(var.agents) == 1
+ error_message = "agents should have one entry"
+ }
+}
diff --git a/registry/coder-labs/modules/omnigent/scripts/install.sh.tftpl b/registry/coder-labs/modules/omnigent/scripts/install.sh.tftpl
new file mode 100644
index 000000000..b042b025b
--- /dev/null
+++ b/registry/coder-labs/modules/omnigent/scripts/install.sh.tftpl
@@ -0,0 +1,74 @@
+#!/bin/bash
+set -euo pipefail
+
+BOLD='\033[0;1m'
+
+ARG_OMNIGENT_VERSION=$(echo -n '${ARG_OMNIGENT_VERSION_B64}' | base64 -d)
+# Empty = latest; non-empty = pinned version
+ARG_PORT='${ARG_PORT}'
+ARG_WRITE_SERVER_CONFIG='${ARG_WRITE_SERVER_CONFIG}'
+ARG_SERVER_CONFIG=$(echo -n '${ARG_SERVER_CONFIG_B64}' | base64 -d)
+ARG_SERVER_CONFIG_FILE='${ARG_SERVER_CONFIG_FILE}'
+ARG_SERVER_CONFIG_DIR='${ARG_SERVER_CONFIG_DIR}'
+ARG_AGENTS=$(echo -n '${ARG_AGENTS_B64}' | base64 -d)
+ARG_AGENTS_DIR='${ARG_AGENTS_DIR}'
+
+export PATH="$${HOME}/.local/bin:$${PATH}"
+
+echo "--------------------------------"
+printf "omnigent_version: %s\n" "$${ARG_OMNIGENT_VERSION:-latest}"
+printf "port: %s\n" "$${ARG_PORT}"
+printf "write_server_config: %s\n" "$${ARG_WRITE_SERVER_CONFIG}"
+echo "--------------------------------"
+
+if ! command -v curl >/dev/null 2>&1; then
+ echo "ERROR: curl is required to install uv and Omnigent." >&2
+ exit 1
+fi
+
+if ! command -v uv >/dev/null 2>&1; then
+ printf "%s Installing uv\n" "$${BOLD}"
+ curl -LsSf https://astral.sh/uv/install.sh | sh
+ export PATH="$${HOME}/.local/bin:$${PATH}"
+fi
+
+if ! command -v uv >/dev/null 2>&1; then
+ echo "ERROR: uv installation failed. Install from https://docs.astral.sh/uv/getting-started/installation/, then rerun." >&2
+ exit 1
+fi
+
+printf "%s Found uv: %s\n" "$${BOLD}" "$(uv --version)"
+
+# Install omnigent via official installer
+INSTALL_ARGS="--non-interactive"
+if [ -n "$${ARG_OMNIGENT_VERSION}" ]; then
+ INSTALL_ARGS="$${INSTALL_ARGS} --version $${ARG_OMNIGENT_VERSION}"
+fi
+printf "%s Installing omnigent%s\n" "$${BOLD}" "$${ARG_OMNIGENT_VERSION:+ $${ARG_OMNIGENT_VERSION}}"
+# shellcheck disable=SC2086
+curl -fsSL https://omnigent.ai/install.sh | sh -s -- $${INSTALL_ARGS}
+
+export PATH="$${HOME}/.local/bin:$${PATH}"
+
+printf "%s Installed omnigent: %s\n" "$${BOLD}" "$(omnigent --version)"
+
+# Configure client to point to the local server
+omnigent config set server=http://localhost:$${ARG_PORT}
+
+# Write server config file if provided
+if [ "$${ARG_WRITE_SERVER_CONFIG}" = "true" ]; then
+ mkdir -p "$${ARG_SERVER_CONFIG_DIR}"
+ printf "%s Writing server config to %s\n" "$${BOLD}" "$${ARG_SERVER_CONFIG_FILE}"
+ echo "$${ARG_SERVER_CONFIG}" > "$${ARG_SERVER_CONFIG_FILE}"
+fi
+
+# Write agent YAML files
+if [ -n "$${ARG_AGENTS}" ]; then
+ mkdir -p "$${ARG_AGENTS_DIR}"
+ while IFS=$'\t' read -r agent_name agent_content_b64; do
+ [ -z "$${agent_name}" ] && continue
+ agent_file="$${ARG_AGENTS_DIR}/$${agent_name}.yaml"
+ printf "%s Writing agent: %s -> %s\n" "$${BOLD}" "$${agent_name}" "$${agent_file}"
+ echo -n "$${agent_content_b64}" | base64 -d > "$${agent_file}"
+ done <<< "$${ARG_AGENTS}"
+fi
diff --git a/registry/coder-labs/modules/omnigent/scripts/start.sh.tftpl b/registry/coder-labs/modules/omnigent/scripts/start.sh.tftpl
new file mode 100644
index 000000000..f569fbc6d
--- /dev/null
+++ b/registry/coder-labs/modules/omnigent/scripts/start.sh.tftpl
@@ -0,0 +1,68 @@
+#!/bin/bash
+set -euo pipefail
+
+export PATH="$${HOME}/.local/bin:$${PATH}"
+
+MODULE_DIR="$${HOME}/.coder-modules/coder-labs/omnigent"
+START_LOG="$${MODULE_DIR}/logs/start.log"
+SERVER_LOG="$${MODULE_DIR}/logs/server.log"
+HOST_LOG="$${MODULE_DIR}/logs/host.log"
+ARG_PORT='${ARG_PORT}'
+ARG_EFFECTIVE_SERVER_CONFIG_PATH='${ARG_EFFECTIVE_SERVER_CONFIG_PATH}'
+ARG_AGENTS_DIR='${ARG_AGENTS_DIR}'
+
+mkdir -p "$${MODULE_DIR}/logs"
+
+# Derive a stable admin password from the workspace ID (first 16 hex chars)
+OMNIGENT_ADMIN_PASSWORD=$(echo -n "$${CODER_WORKSPACE_ID}" | tr -d '-' | cut -c1-16)
+
+if ! curl -sf "http://localhost:$${ARG_PORT}/health" &>/dev/null; then
+ echo "Starting Omnigent server on port $${ARG_PORT}..."
+
+ # Build server flags
+ SERVER_FLAGS="--host 127.0.0.1 --port $${ARG_PORT} --no-open"
+
+ # Add config file if set and present
+ if [ -n "$${ARG_EFFECTIVE_SERVER_CONFIG_PATH}" ] && [ -f "$${ARG_EFFECTIVE_SERVER_CONFIG_PATH}" ]; then
+ SERVER_FLAGS="$${SERVER_FLAGS} -c $${ARG_EFFECTIVE_SERVER_CONFIG_PATH}"
+ echo "Using server config: $${ARG_EFFECTIVE_SERVER_CONFIG_PATH}"
+ fi
+
+ # Add pre-registered agent YAML files
+ if [ -d "$${ARG_AGENTS_DIR}" ]; then
+ for agent_file in "$${ARG_AGENTS_DIR}"/*.yaml; do
+ [ -f "$${agent_file}" ] || continue
+ SERVER_FLAGS="$${SERVER_FLAGS} --agent $${agent_file}"
+ echo "Registering agent: $${agent_file}"
+ done
+ fi
+
+ export OMNIGENT_ACCOUNTS_INIT_ADMIN_PASSWORD="$${OMNIGENT_ADMIN_PASSWORD}"
+ # shellcheck disable=SC2086
+ nohup omnigent server $${SERVER_FLAGS} >> "$${SERVER_LOG}" 2>&1 &
+else
+ echo "Omnigent server already running on port $${ARG_PORT}, skipping start."
+fi
+
+echo "Waiting for Omnigent server..."
+for i in $(seq 1 90); do
+ if curl -sf "http://localhost:$${ARG_PORT}/health" &>/dev/null; then
+ echo "Omnigent server is ready."
+ break
+ fi
+ if [ "$${i}" -eq 90 ]; then
+ echo "ERROR: Omnigent server did not start within 90 seconds." >&2
+ cat "$${SERVER_LOG}" >&2 || true
+ exit 1
+ fi
+ sleep 1
+done
+
+# Register local workspace as a host. `omnigent host` stays attached, so run it
+# in the background to let the Coder startup script finish.
+if ! pgrep -f "[o]mnigent host" >/dev/null 2>&1; then
+ echo "Starting Omnigent host..."
+ nohup omnigent host "" >> "$${HOST_LOG}" 2>&1 &
+else
+ echo "Omnigent host already running, skipping start."
+fi
diff --git a/registry/coder-labs/templates/omnigent-workspace/README.md b/registry/coder-labs/templates/omnigent-workspace/README.md
new file mode 100644
index 000000000..8cef9480b
--- /dev/null
+++ b/registry/coder-labs/templates/omnigent-workspace/README.md
@@ -0,0 +1,49 @@
+---
+display_name: Omnigent Workspace
+icon: ../../../../.icons/omnigent.svg
+description: Docker workspace with Omnigent, Claude Code, and Codex pre-installed.
+verified: false
+tags: [docker, omnigent, claude-code, codex, ai, multi-agent]
+---
+
+# Omnigent Workspace
+
+A Docker-based workspace that combines three AI agent modules:
+
+- **[Omnigent](https://registry.coder.com/modules/coder-labs/omnigent)** — private multi-agent coding orchestrator server
+- **[Claude Code](https://registry.coder.com/modules/coder/claude-code)** — Anthropic's Claude in your terminal, authenticated through Coder AI Gateway
+- **[Codex](https://registry.coder.com/modules/coder-labs/codex)** — OpenAI's Codex CLI, authenticated through Coder AI Gateway
+
+Each workspace runs its own isolated Omnigent server. The admin password is derived from the workspace ID at runtime and never stored in Terraform state.
+
+```tf
+module "codex" {
+ source = "registry.coder.com/coder-labs/codex/coder"
+ version = "5.2.0"
+
+ agent_id = coder_agent.main.id
+ enable_ai_gateway = true
+}
+
+module "claude_code" {
+ source = "registry.coder.com/coder/claude-code/coder"
+ version = "5.2.0"
+
+ agent_id = coder_agent.main.id
+ enable_ai_gateway = true
+}
+
+module "omnigent" {
+ source = "registry.coder.com/coder-labs/omnigent/coder"
+ version = "1.0.0"
+
+ agent_id = coder_agent.main.id
+}
+```
+
+## Prerequisites
+
+- Docker with `sysbox-runc` runtime installed on the Coder host
+- Coder Premium with AI Gateway enabled
+
+The template installs `jq`, `tmux`, and `bubblewrap` before the AI tools start because the Claude Code module uses `jq` for setup and Omnigent launches the Claude Code and Codex harnesses through local terminal sessions.
diff --git a/registry/coder-labs/templates/omnigent-workspace/main.tf b/registry/coder-labs/templates/omnigent-workspace/main.tf
new file mode 100644
index 000000000..a3727f068
--- /dev/null
+++ b/registry/coder-labs/templates/omnigent-workspace/main.tf
@@ -0,0 +1,189 @@
+terraform {
+ required_providers {
+ coder = {
+ source = "coder/coder"
+ version = ">= 2.13"
+ }
+ docker = {
+ source = "kreuzwerker/docker"
+ version = "~> 4.0"
+ }
+ }
+}
+
+provider "coder" {}
+provider "docker" {}
+
+data "coder_workspace" "me" {}
+data "coder_workspace_owner" "me" {}
+data "coder_provisioner" "me" {}
+
+locals {
+ ai_tools_pre_install_script = <<-EOT
+ #!/bin/bash
+ set -euo pipefail
+
+ if command -v apt-get >/dev/null 2>&1; then
+ (
+ flock 9
+ sudo apt-get update
+ sudo apt-get install -y curl ca-certificates jq tmux bubblewrap
+ ) 9>/tmp/coder-ai-tools-apt.lock
+ fi
+ EOT
+}
+
+resource "coder_agent" "main" {
+ arch = data.coder_provisioner.me.arch
+ os = "linux"
+ startup_script = <<-EOT
+ #!/bin/bash
+ set -e
+ if [ ! -f ~/.init_done ]; then
+ cp -rT /etc/skel ~ 2>/dev/null || true
+ touch ~/.init_done
+ fi
+ EOT
+
+ env = {
+ GIT_AUTHOR_NAME = coalesce(data.coder_workspace_owner.me.full_name, data.coder_workspace_owner.me.name)
+ GIT_AUTHOR_EMAIL = data.coder_workspace_owner.me.email
+ GIT_COMMITTER_NAME = coalesce(data.coder_workspace_owner.me.full_name, data.coder_workspace_owner.me.name)
+ GIT_COMMITTER_EMAIL = data.coder_workspace_owner.me.email
+ }
+
+ metadata {
+ display_name = "CPU Usage"
+ key = "0_cpu_usage"
+ script = "coder stat cpu"
+ interval = 10
+ timeout = 1
+ }
+
+ metadata {
+ display_name = "RAM Usage"
+ key = "1_ram_usage"
+ script = "coder stat mem"
+ interval = 10
+ timeout = 1
+ }
+
+ metadata {
+ display_name = "Home Disk"
+ key = "2_home_disk"
+ script = "coder stat disk --path $${HOME}"
+ interval = 60
+ timeout = 1
+ }
+}
+
+module "codex" {
+ source = "registry.coder.com/coder-labs/codex/coder"
+ version = "5.2.0"
+
+ agent_id = coder_agent.main.id
+ enable_ai_gateway = true
+ pre_install_script = local.ai_tools_pre_install_script
+}
+
+module "claude_code" {
+ source = "registry.coder.com/coder/claude-code/coder"
+ version = "5.2.0"
+
+ agent_id = coder_agent.main.id
+ enable_ai_gateway = true
+ pre_install_script = local.ai_tools_pre_install_script
+}
+
+module "omnigent" {
+ source = "registry.coder.com/coder-labs/omnigent/coder"
+ version = "1.0.0"
+
+ agent_id = coder_agent.main.id
+
+ # Omnigent snapshots the host's available tools when the host starts. Wait for
+ # Claude Code and Codex to install and configure AI Gateway first, otherwise
+ # the Omnigent UI shows these harnesses as needing setup until the host restarts.
+ pre_install_script = <<-EOT
+ #!/bin/bash
+ set -euo pipefail
+ coder exp sync want coder-labs-omnigent-ai-tools ${join(" ", concat(module.claude_code.scripts, module.codex.scripts))}
+ coder exp sync start coder-labs-omnigent-ai-tools
+ coder exp sync complete coder-labs-omnigent-ai-tools
+ EOT
+}
+
+resource "docker_volume" "home_volume" {
+ name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}-home"
+ lifecycle {
+ ignore_changes = all
+ }
+ labels {
+ label = "coder.owner"
+ value = data.coder_workspace_owner.me.name
+ }
+ labels {
+ label = "coder.owner_id"
+ value = data.coder_workspace_owner.me.id
+ }
+ labels {
+ label = "coder.workspace_id"
+ value = data.coder_workspace.me.id
+ }
+ labels {
+ label = "coder.workspace_name_at_creation"
+ value = data.coder_workspace.me.name
+ }
+}
+
+data "docker_registry_image" "workspace" {
+ name = "codercom/enterprise-base:ubuntu"
+}
+
+resource "docker_image" "workspace" {
+ name = "codercom/enterprise-base@${data.docker_registry_image.workspace.sha256_digest}"
+ pull_triggers = [data.docker_registry_image.workspace.sha256_digest]
+ keep_locally = true
+}
+
+resource "docker_container" "workspace" {
+ count = data.coder_workspace.me.start_count
+ image = docker_image.workspace.image_id
+ name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}"
+ hostname = lower(data.coder_workspace.me.name)
+ runtime = "sysbox-runc"
+
+ entrypoint = ["sh", "-c", replace(coder_agent.main.init_script, "/localhost|127\\.0\\.0\\.1/", "host.docker.internal")]
+
+ env = [
+ "CODER_AGENT_TOKEN=${coder_agent.main.token}",
+ ]
+
+ host {
+ host = "host.docker.internal"
+ ip = "host-gateway"
+ }
+
+ volumes {
+ container_path = "/home/coder"
+ volume_name = docker_volume.home_volume.name
+ read_only = false
+ }
+
+ labels {
+ label = "coder.owner"
+ value = data.coder_workspace_owner.me.name
+ }
+ labels {
+ label = "coder.owner_id"
+ value = data.coder_workspace_owner.me.id
+ }
+ labels {
+ label = "coder.workspace_id"
+ value = data.coder_workspace.me.id
+ }
+ labels {
+ label = "coder.workspace_name"
+ value = data.coder_workspace.me.name
+ }
+}