From 051e45109464e81b0f296817f2d72d8b3d0fbc28 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Tue, 16 Jun 2026 12:48:54 +0500 Subject: [PATCH 01/10] feat(coder-labs): add Omnigent multi-agent server module --- .icons/omnigent.svg | 1 + .../coder-labs/modules/omnigent/README.md | 85 +++++++++ registry/coder-labs/modules/omnigent/main.tf | 102 +++++++++++ .../modules/omnigent/main.tftest.hcl | 144 +++++++++++++++ .../modules/omnigent/scripts/install.sh.tftpl | 38 ++++ .../modules/omnigent/scripts/start.sh.tftpl | 37 ++++ .../templates/omnigent-workspace/README.md | 44 +++++ .../templates/omnigent-workspace/main.tf | 170 ++++++++++++++++++ 8 files changed, 621 insertions(+) create mode 100644 .icons/omnigent.svg create mode 100644 registry/coder-labs/modules/omnigent/README.md create mode 100644 registry/coder-labs/modules/omnigent/main.tf create mode 100644 registry/coder-labs/modules/omnigent/main.tftest.hcl create mode 100644 registry/coder-labs/modules/omnigent/scripts/install.sh.tftpl create mode 100644 registry/coder-labs/modules/omnigent/scripts/start.sh.tftpl create mode 100644 registry/coder-labs/templates/omnigent-workspace/README.md create mode 100644 registry/coder-labs/templates/omnigent-workspace/main.tf diff --git a/.icons/omnigent.svg b/.icons/omnigent.svg new file mode 100644 index 000000000..c65fe35ff --- /dev/null +++ b/.icons/omnigent.svg @@ -0,0 +1 @@ +O diff --git a/registry/coder-labs/modules/omnigent/README.md b/registry/coder-labs/modules/omnigent/README.md new file mode 100644 index 000000000..2aa70d224 --- /dev/null +++ b/registry/coder-labs/modules/omnigent/README.md @@ -0,0 +1,85 @@ +--- +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 `uv tool install`, 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 + +### Standalone with default settings + +```tf +module "omnigent" { + source = "registry.coder.com/coder-labs/omnigent/coder" + version = "1.0.0" + agent_id = coder_agent.main.id +} +``` + +### 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: + +```tf +module "omnigent" { + source = "registry.coder.com/coder-labs/omnigent/coder" + version = "1.0.0" + agent_id = coder_agent.main.id +} + +module "codex" { + source = "registry.coder.com/coder-labs/codex/coder" + version = "5.0.0" + agent_id = coder_agent.main.id + openai_api_key = var.openai_api_key +} + +module "claude_code" { + source = "registry.coder.com/coder/claude-code/coder" + version = ">= 4.0.0" + agent_id = coder_agent.main.id + anthropic_api_key = var.anthropic_api_key +} +``` + +## Troubleshooting + +Server logs are written to `~/.coder-modules/coder-labs/omnigent/logs/start.log`. If the Omnigent app shows as unhealthy or the server fails to start, check: + +```bash +cat ~/.coder-modules/coder-labs/omnigent/logs/start.log +cat ~/.coder-modules/coder-labs/omnigent/logs/install.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" +``` diff --git a/registry/coder-labs/modules/omnigent/main.tf b/registry/coder-labs/modules/omnigent/main.tf new file mode 100644 index 000000000..c6c9794ca --- /dev/null +++ b/registry/coder-labs/modules/omnigent/main.tf @@ -0,0 +1,102 @@ +terraform { + required_version = ">= 1.9" + + required_providers { + coder = { + source = "coder/coder" + version = ">= 2.12" + } + } +} + +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 +} + +locals { + install_script = templatefile("${path.module}/scripts/install.sh.tftpl", { + ARG_OMNIGENT_VERSION = var.omnigent_version + ARG_PORT = tostring(var.port) + }) + start_script = templatefile("${path.module}/scripts/start.sh.tftpl", { + ARG_PORT = tostring(var.port) + }) +} + +module "coder_utils" { + source = "registry.coder.com/coder/coder-utils/coder" + version = "0.0.1" + + agent_id = var.agent_id + module_directory = "$HOME/.coder-modules/coder-labs/omnigent" + display_name_prefix = "Omnigent" + icon = var.icon + 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 +} 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..1a4c9d421 --- /dev/null +++ b/registry/coder-labs/modules/omnigent/main.tftest.hcl @@ -0,0 +1,144 @@ +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_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] +} 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..f8e91c2c0 --- /dev/null +++ b/registry/coder-labs/modules/omnigent/scripts/install.sh.tftpl @@ -0,0 +1,38 @@ +#!/bin/bash +set -euo pipefail + +BOLD='\033[0;1m' + +ARG_OMNIGENT_VERSION='${ARG_OMNIGENT_VERSION}' +ARG_PORT='${ARG_PORT}' + +echo "--------------------------------" +printf "omnigent_version: %s\n" "$${ARG_OMNIGENT_VERSION}" +printf "port: %s\n" "$${ARG_PORT}" +echo "--------------------------------" + +# Install uv if missing +if ! command -v uv &>/dev/null; then + printf "%s Installing uv\n" "$${BOLD}" + curl -LsSf https://astral.sh/uv/install.sh | sh +fi +export PATH="$${HOME}/.local/bin:$${PATH}" + +# Install or upgrade omnigent +if [ "$${ARG_OMNIGENT_VERSION}" = "latest" ]; then + if ! command -v omnigent &>/dev/null; then + printf "%s Installing omnigent (latest)\n" "$${BOLD}" + uv tool install omnigent + else + printf "%s Upgrading omnigent\n" "$${BOLD}" + uv tool upgrade omnigent 2>/dev/null || true + fi +else + printf "%s Installing omnigent %s\n" "$${BOLD}" "$${ARG_OMNIGENT_VERSION}" + uv tool install "omnigent==$${ARG_OMNIGENT_VERSION}" --force-reinstall +fi +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} 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..bca40a954 --- /dev/null +++ b/registry/coder-labs/modules/omnigent/scripts/start.sh.tftpl @@ -0,0 +1,37 @@ +#!/bin/bash +set -euo pipefail + +MODULE_DIR="$${HOME}/.coder-modules/coder-labs/omnigent" +LOG_FILE="$${MODULE_DIR}/logs/start.log" +ARG_PORT='${ARG_PORT}' + +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}..." + export OMNIGENT_ACCOUNTS_INIT_ADMIN_PASSWORD="$${OMNIGENT_ADMIN_PASSWORD}" + nohup omnigent server --host 127.0.0.1 --port "$${ARG_PORT}" --no-open \ + >> "$${LOG_FILE}" 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 "$${LOG_FILE}" >&2 || true + exit 1 + fi + sleep 1 +done + +# Register local workspace as a host +omnigent host "" 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..1d00c75ba --- /dev/null +++ b/registry/coder-labs/templates/omnigent-workspace/README.md @@ -0,0 +1,44 @@ +--- +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 +- **[Codex](https://registry.coder.com/modules/coder-labs/codex)** — OpenAI's Codex CLI + +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 "omnigent" { + source = "registry.coder.com/coder-labs/omnigent/coder" + version = "1.0.0" + agent_id = coder_agent.main.id +} + +module "codex" { + source = "registry.coder.com/coder-labs/codex/coder" + version = "5.0.0" + agent_id = coder_agent.main.id + openai_api_key = var.openai_api_key +} + +module "claude_code" { + source = "registry.coder.com/coder/claude-code/coder" + version = ">= 4.0.0" + agent_id = coder_agent.main.id + anthropic_api_key = var.anthropic_api_key +} +``` + +## Prerequisites + +- Docker with `sysbox-runc` runtime installed on the Coder host +- `ANTHROPIC_API_KEY` and `OPENAI_API_KEY` set as Coder template variables 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..b72dfaf16 --- /dev/null +++ b/registry/coder-labs/templates/omnigent-workspace/main.tf @@ -0,0 +1,170 @@ +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" {} + +variable "anthropic_api_key" { + description = "Anthropic API key for Claude Code." + type = string + sensitive = true +} + +variable "openai_api_key" { + description = "OpenAI API key for Codex." + type = string + sensitive = true +} + +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 "omnigent" { + source = "registry.coder.com/coder-labs/omnigent/coder" + version = "1.0.0" + agent_id = coder_agent.main.id +} + +module "codex" { + source = "registry.coder.com/coder-labs/codex/coder" + version = "5.0.0" + agent_id = coder_agent.main.id + openai_api_key = var.openai_api_key +} + +module "claude_code" { + source = "registry.coder.com/coder/claude-code/coder" + version = ">= 4.0.0" + agent_id = coder_agent.main.id + anthropic_api_key = var.anthropic_api_key +} + +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 + } +} From 74b69f135f64ab13a2aa57b2ee899a93bf9ccd2c Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Tue, 16 Jun 2026 13:07:50 +0500 Subject: [PATCH 02/10] fix(coder-labs/omnigent): blocking fixes + policies/agents inputs Blocking fixes: - Add PATH export at top of start.sh so omnigent is found after install - Separate server.log from start.log (nohup concurrent write conflict) - Bump coder provider constraint from >= 2.12 to >= 2.13 (matches coder-utils) - Base64-encode omnigent version in install.sh (injection hardening) - Surface upgrade failures as warnings instead of silencing them New inputs: - server_config: inline YAML written to module dir and passed as -c - server_config_path: path to existing config file, mutually exclusive with server_config - agents: list of {name, content} pre-registered at startup via --agent flags - pre_install_script / post_install_script: pass-through to coder-utils New output: server_config_path (effective config path or empty string) 13/13 tests pass. --- .../coder-labs/modules/omnigent/README.md | 77 ++++++++++++++++--- registry/coder-labs/modules/omnigent/main.tf | 74 ++++++++++++++++-- .../modules/omnigent/main.tftest.hcl | 59 ++++++++++++++ .../modules/omnigent/scripts/install.sh.tftpl | 38 +++++++-- .../modules/omnigent/scripts/start.sh.tftpl | 32 +++++++- 5 files changed, 255 insertions(+), 25 deletions(-) diff --git a/registry/coder-labs/modules/omnigent/README.md b/registry/coder-labs/modules/omnigent/README.md index 2aa70d224..356f4e4fd 100644 --- a/registry/coder-labs/modules/omnigent/README.md +++ b/registry/coder-labs/modules/omnigent/README.md @@ -22,16 +22,6 @@ module "omnigent" { ## Examples -### Standalone with default settings - -```tf -module "omnigent" { - source = "registry.coder.com/coder-labs/omnigent/coder" - version = "1.0.0" - agent_id = coder_agent.main.id -} -``` - ### With a custom port ```tf @@ -69,11 +59,68 @@ module "claude_code" { } ``` +### 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 -Server logs are written to `~/.coder-modules/coder-labs/omnigent/logs/start.log`. If the Omnigent app shows as unhealthy or the server fails to start, check: +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 ``` @@ -83,3 +130,11 @@ The health endpoint is available at `http://localhost:/health`. You can ch ```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 index c6c9794ca..ab62bcf57 100644 --- a/registry/coder-labs/modules/omnigent/main.tf +++ b/registry/coder-labs/modules/omnigent/main.tf @@ -4,7 +4,7 @@ terraform { required_providers { coder = { source = "coder/coder" - version = ">= 2.12" + version = ">= 2.13" } } } @@ -52,13 +52,70 @@ variable "order" { 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 = var.omnigent_version - ARG_PORT = tostring(var.port) + ARG_OMNIGENT_VERSION_IS_LATEST = tostring(var.omnigent_version == "latest") + 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_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 }) } @@ -67,9 +124,11 @@ module "coder_utils" { version = "0.0.1" agent_id = var.agent_id - module_directory = "$HOME/.coder-modules/coder-labs/omnigent" + 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 } @@ -100,3 +159,8 @@ 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 index 1a4c9d421..ccc2e48c3 100644 --- a/registry/coder-labs/modules/omnigent/main.tftest.hcl +++ b/registry/coder-labs/modules/omnigent/main.tftest.hcl @@ -142,3 +142,62 @@ run "test_invalid_share" { 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 index f8e91c2c0..78af0118a 100644 --- a/registry/coder-labs/modules/omnigent/scripts/install.sh.tftpl +++ b/registry/coder-labs/modules/omnigent/scripts/install.sh.tftpl @@ -3,29 +3,39 @@ set -euo pipefail BOLD='\033[0;1m' -ARG_OMNIGENT_VERSION='${ARG_OMNIGENT_VERSION}' +ARG_OMNIGENT_VERSION_IS_LATEST='${ARG_OMNIGENT_VERSION_IS_LATEST}' +ARG_OMNIGENT_VERSION=$(echo -n '${ARG_OMNIGENT_VERSION_B64}' | base64 -d) 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}' echo "--------------------------------" -printf "omnigent_version: %s\n" "$${ARG_OMNIGENT_VERSION}" +printf "omnigent_version (latest=%s): %s\n" "$${ARG_OMNIGENT_VERSION_IS_LATEST}" "$${ARG_OMNIGENT_VERSION:-latest}" printf "port: %s\n" "$${ARG_PORT}" +printf "write_server_config: %s\n" "$${ARG_WRITE_SERVER_CONFIG}" echo "--------------------------------" +export PATH="$${HOME}/.local/bin:$${PATH}" + # Install uv if missing if ! command -v uv &>/dev/null; then printf "%s Installing uv\n" "$${BOLD}" curl -LsSf https://astral.sh/uv/install.sh | sh + export PATH="$${HOME}/.local/bin:$${PATH}" fi -export PATH="$${HOME}/.local/bin:$${PATH}" # Install or upgrade omnigent -if [ "$${ARG_OMNIGENT_VERSION}" = "latest" ]; then +if [ "$${ARG_OMNIGENT_VERSION_IS_LATEST}" = "true" ]; then if ! command -v omnigent &>/dev/null; then printf "%s Installing omnigent (latest)\n" "$${BOLD}" uv tool install omnigent else printf "%s Upgrading omnigent\n" "$${BOLD}" - uv tool upgrade omnigent 2>/dev/null || true + uv tool upgrade omnigent || echo "Warning: upgrade failed, continuing with existing version" fi else printf "%s Installing omnigent %s\n" "$${BOLD}" "$${ARG_OMNIGENT_VERSION}" @@ -36,3 +46,21 @@ 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 index bca40a954..f2aae552e 100644 --- a/registry/coder-labs/modules/omnigent/scripts/start.sh.tftpl +++ b/registry/coder-labs/modules/omnigent/scripts/start.sh.tftpl @@ -1,9 +1,14 @@ #!/bin/bash set -euo pipefail +export PATH="$${HOME}/.local/bin:$${PATH}" + MODULE_DIR="$${HOME}/.coder-modules/coder-labs/omnigent" -LOG_FILE="$${MODULE_DIR}/logs/start.log" +START_LOG="$${MODULE_DIR}/logs/start.log" +SERVER_LOG="$${MODULE_DIR}/logs/server.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" @@ -12,9 +17,28 @@ OMNIGENT_ADMIN_PASSWORD=$(echo -n "$${CODER_WORKSPACE_ID}" | tr -d '-' | cut -c1 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}" - nohup omnigent server --host 127.0.0.1 --port "$${ARG_PORT}" --no-open \ - >> "$${LOG_FILE}" 2>&1 & + # 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 @@ -27,7 +51,7 @@ for i in $(seq 1 90); do fi if [ "$${i}" -eq 90 ]; then echo "ERROR: Omnigent server did not start within 90 seconds." >&2 - cat "$${LOG_FILE}" >&2 || true + cat "$${SERVER_LOG}" >&2 || true exit 1 fi sleep 1 From 8a3155e787f8c4bb9de760a040d65b33f8c0623a Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Tue, 16 Jun 2026 13:09:43 +0500 Subject: [PATCH 03/10] refactor(coder-labs/omnigent): switch to official installer --- .../coder-labs/modules/omnigent/README.md | 2 +- registry/coder-labs/modules/omnigent/main.tf | 17 +++++----- .../modules/omnigent/scripts/install.sh.tftpl | 32 ++++++------------- 3 files changed, 18 insertions(+), 33 deletions(-) diff --git a/registry/coder-labs/modules/omnigent/README.md b/registry/coder-labs/modules/omnigent/README.md index 356f4e4fd..89e672a16 100644 --- a/registry/coder-labs/modules/omnigent/README.md +++ b/registry/coder-labs/modules/omnigent/README.md @@ -10,7 +10,7 @@ tags: [agent, omnigent, ai, multi-agent] 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 `uv tool install`, 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. +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" { diff --git a/registry/coder-labs/modules/omnigent/main.tf b/registry/coder-labs/modules/omnigent/main.tf index ab62bcf57..7b758a3d1 100644 --- a/registry/coder-labs/modules/omnigent/main.tf +++ b/registry/coder-labs/modules/omnigent/main.tf @@ -101,15 +101,14 @@ locals { ) install_script = templatefile("${path.module}/scripts/install.sh.tftpl", { - ARG_OMNIGENT_VERSION_IS_LATEST = tostring(var.omnigent_version == "latest") - 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 + 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", { diff --git a/registry/coder-labs/modules/omnigent/scripts/install.sh.tftpl b/registry/coder-labs/modules/omnigent/scripts/install.sh.tftpl index 78af0118a..948707f12 100644 --- a/registry/coder-labs/modules/omnigent/scripts/install.sh.tftpl +++ b/registry/coder-labs/modules/omnigent/scripts/install.sh.tftpl @@ -3,8 +3,8 @@ set -euo pipefail BOLD='\033[0;1m' -ARG_OMNIGENT_VERSION_IS_LATEST='${ARG_OMNIGENT_VERSION_IS_LATEST}' 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) @@ -14,34 +14,20 @@ ARG_AGENTS=$(echo -n '${ARG_AGENTS_B64}' | base64 -d) ARG_AGENTS_DIR='${ARG_AGENTS_DIR}' echo "--------------------------------" -printf "omnigent_version (latest=%s): %s\n" "$${ARG_OMNIGENT_VERSION_IS_LATEST}" "$${ARG_OMNIGENT_VERSION:-latest}" +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 "--------------------------------" -export PATH="$${HOME}/.local/bin:$${PATH}" - -# Install uv if missing -if ! command -v uv &>/dev/null; then - printf "%s Installing uv\n" "$${BOLD}" - curl -LsSf https://astral.sh/uv/install.sh | sh - export PATH="$${HOME}/.local/bin:$${PATH}" +# 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} -# Install or upgrade omnigent -if [ "$${ARG_OMNIGENT_VERSION_IS_LATEST}" = "true" ]; then - if ! command -v omnigent &>/dev/null; then - printf "%s Installing omnigent (latest)\n" "$${BOLD}" - uv tool install omnigent - else - printf "%s Upgrading omnigent\n" "$${BOLD}" - uv tool upgrade omnigent || echo "Warning: upgrade failed, continuing with existing version" - fi -else - printf "%s Installing omnigent %s\n" "$${BOLD}" "$${ARG_OMNIGENT_VERSION}" - uv tool install "omnigent==$${ARG_OMNIGENT_VERSION}" --force-reinstall -fi -export PATH="$${HOME}/.local/bin:$${PATH}" printf "%s Installed omnigent: %s\n" "$${BOLD}" "$(omnigent --version)" # Configure client to point to the local server From bd1dbaeedcc8a66b343305bada26cb658b8bf567 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Tue, 16 Jun 2026 13:16:26 +0500 Subject: [PATCH 04/10] chore: replace placeholder omnigent icon with official SVG logo --- .icons/omnigent.svg | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/.icons/omnigent.svg b/.icons/omnigent.svg index c65fe35ff..b70b60659 100644 --- a/.icons/omnigent.svg +++ b/.icons/omnigent.svg @@ -1 +1,44 @@ -O + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From c337dcc9f5d470a7218049f03ca43b49d111534b Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Tue, 23 Jun 2026 19:01:11 +0500 Subject: [PATCH 05/10] fix(coder-labs): install uv before Omnigent setup --- .../modules/omnigent/main.tftest.hcl | 18 +++++++++++++++ .../modules/omnigent/scripts/install.sh.tftpl | 22 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/registry/coder-labs/modules/omnigent/main.tftest.hcl b/registry/coder-labs/modules/omnigent/main.tftest.hcl index ccc2e48c3..e17cde796 100644 --- a/registry/coder-labs/modules/omnigent/main.tftest.hcl +++ b/registry/coder-labs/modules/omnigent/main.tftest.hcl @@ -96,6 +96,24 @@ run "test_scripts_output" { } } +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_port_output" { command = plan diff --git a/registry/coder-labs/modules/omnigent/scripts/install.sh.tftpl b/registry/coder-labs/modules/omnigent/scripts/install.sh.tftpl index 948707f12..b042b025b 100644 --- a/registry/coder-labs/modules/omnigent/scripts/install.sh.tftpl +++ b/registry/coder-labs/modules/omnigent/scripts/install.sh.tftpl @@ -13,12 +13,32 @@ 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 @@ -28,6 +48,8 @@ printf "%s Installing omnigent%s\n" "$${BOLD}" "$${ARG_OMNIGENT_VERSION:+ $${ARG # 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 From 8293a8dc2745b41c7cbf7af1e703f5b816ad872e Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Tue, 23 Jun 2026 19:09:48 +0500 Subject: [PATCH 06/10] fix(coder-labs): run Omnigent host in background --- registry/coder-labs/modules/omnigent/README.md | 1 + .../modules/omnigent/main.tftest.hcl | 18 ++++++++++++++++++ .../modules/omnigent/scripts/start.sh.tftpl | 11 +++++++++-- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/registry/coder-labs/modules/omnigent/README.md b/registry/coder-labs/modules/omnigent/README.md index 89e672a16..64e679f4f 100644 --- a/registry/coder-labs/modules/omnigent/README.md +++ b/registry/coder-labs/modules/omnigent/README.md @@ -123,6 +123,7 @@ Script logs are written to `~/.coder-modules/coder-labs/omnigent/logs/`. If the 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: diff --git a/registry/coder-labs/modules/omnigent/main.tftest.hcl b/registry/coder-labs/modules/omnigent/main.tftest.hcl index e17cde796..341703165 100644 --- a/registry/coder-labs/modules/omnigent/main.tftest.hcl +++ b/registry/coder-labs/modules/omnigent/main.tftest.hcl @@ -114,6 +114,24 @@ run "test_install_script_installs_uv" { } } +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 diff --git a/registry/coder-labs/modules/omnigent/scripts/start.sh.tftpl b/registry/coder-labs/modules/omnigent/scripts/start.sh.tftpl index f2aae552e..f569fbc6d 100644 --- a/registry/coder-labs/modules/omnigent/scripts/start.sh.tftpl +++ b/registry/coder-labs/modules/omnigent/scripts/start.sh.tftpl @@ -6,6 +6,7 @@ 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}' @@ -57,5 +58,11 @@ for i in $(seq 1 90); do sleep 1 done -# Register local workspace as a host -omnigent host "" +# 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 From 3221045ba52544c41aa144c85d470898ac45a7b8 Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Tue, 23 Jun 2026 19:22:34 +0500 Subject: [PATCH 07/10] fix(coder-labs): configure Omnigent template with AI Gateway --- .../coder-labs/modules/omnigent/README.md | 31 ++++++----- .../templates/omnigent-workspace/README.md | 35 +++++++------ .../templates/omnigent-workspace/main.tf | 52 ++++++++++--------- 3 files changed, 63 insertions(+), 55 deletions(-) diff --git a/registry/coder-labs/modules/omnigent/README.md b/registry/coder-labs/modules/omnigent/README.md index 64e679f4f..710189b90 100644 --- a/registry/coder-labs/modules/omnigent/README.md +++ b/registry/coder-labs/modules/omnigent/README.md @@ -35,27 +35,30 @@ module "omnigent" { ### With AI tools (Omnigent + Claude Code + Codex) -Compose Omnigent alongside other AI agent modules to create a full multi-agent workspace: +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 "omnigent" { - source = "registry.coder.com/coder-labs/omnigent/coder" - version = "1.0.0" - agent_id = coder_agent.main.id -} - module "codex" { - source = "registry.coder.com/coder-labs/codex/coder" - version = "5.0.0" - agent_id = coder_agent.main.id - openai_api_key = var.openai_api_key + 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" + source = "registry.coder.com/coder/claude-code/coder" + version = ">= 4.0.0" + agent_id = coder_agent.main.id - anthropic_api_key = var.anthropic_api_key + enable_ai_gateway = true +} + +module "omnigent" { + source = "registry.coder.com/coder-labs/omnigent/coder" + version = "1.0.0" + + agent_id = coder_agent.main.id } ``` diff --git a/registry/coder-labs/templates/omnigent-workspace/README.md b/registry/coder-labs/templates/omnigent-workspace/README.md index 1d00c75ba..21bfc0ab9 100644 --- a/registry/coder-labs/templates/omnigent-workspace/README.md +++ b/registry/coder-labs/templates/omnigent-workspace/README.md @@ -11,34 +11,37 @@ tags: [docker, omnigent, claude-code, codex, ai, multi-agent] 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 -- **[Codex](https://registry.coder.com/modules/coder-labs/codex)** — OpenAI's Codex CLI +- **[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 "omnigent" { - source = "registry.coder.com/coder-labs/omnigent/coder" - version = "1.0.0" - agent_id = coder_agent.main.id -} - module "codex" { - source = "registry.coder.com/coder-labs/codex/coder" - version = "5.0.0" - agent_id = coder_agent.main.id - openai_api_key = var.openai_api_key + 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" + source = "registry.coder.com/coder/claude-code/coder" + version = ">= 4.0.0" + agent_id = coder_agent.main.id - anthropic_api_key = var.anthropic_api_key + 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 -- `ANTHROPIC_API_KEY` and `OPENAI_API_KEY` set as Coder template variables +- Coder Premium with AI Gateway enabled diff --git a/registry/coder-labs/templates/omnigent-workspace/main.tf b/registry/coder-labs/templates/omnigent-workspace/main.tf index b72dfaf16..3908881ff 100644 --- a/registry/coder-labs/templates/omnigent-workspace/main.tf +++ b/registry/coder-labs/templates/omnigent-workspace/main.tf @@ -18,18 +18,6 @@ data "coder_workspace" "me" {} data "coder_workspace_owner" "me" {} data "coder_provisioner" "me" {} -variable "anthropic_api_key" { - description = "Anthropic API key for Claude Code." - type = string - sensitive = true -} - -variable "openai_api_key" { - description = "OpenAI API key for Codex." - type = string - sensitive = true -} - resource "coder_agent" "main" { arch = data.coder_provisioner.me.arch os = "linux" @@ -74,24 +62,38 @@ resource "coder_agent" "main" { } } -module "omnigent" { - source = "registry.coder.com/coder-labs/omnigent/coder" - version = "1.0.0" - agent_id = coder_agent.main.id -} - module "codex" { - source = "registry.coder.com/coder-labs/codex/coder" - version = "5.0.0" - agent_id = coder_agent.main.id - openai_api_key = var.openai_api_key + 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" + source = "registry.coder.com/coder/claude-code/coder" + version = ">= 4.0.0" + agent_id = coder_agent.main.id - anthropic_api_key = var.anthropic_api_key + enable_ai_gateway = true +} + +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" { From 88156261b264c1c3cdaea4df5180483555896c0b Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Tue, 23 Jun 2026 20:47:13 +0500 Subject: [PATCH 08/10] fix(coder-labs): install Omnigent workspace tool dependencies --- .../templates/omnigent-workspace/README.md | 2 ++ .../templates/omnigent-workspace/main.tf | 25 ++++++++++++++++--- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/registry/coder-labs/templates/omnigent-workspace/README.md b/registry/coder-labs/templates/omnigent-workspace/README.md index 21bfc0ab9..ab937c976 100644 --- a/registry/coder-labs/templates/omnigent-workspace/README.md +++ b/registry/coder-labs/templates/omnigent-workspace/README.md @@ -45,3 +45,5 @@ module "omnigent" { - Docker with `sysbox-runc` runtime installed on the Coder host - Coder Premium with AI Gateway enabled + +The template installs `tmux` and `bubblewrap` before the AI tools start because 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 index 3908881ff..d137af0e3 100644 --- a/registry/coder-labs/templates/omnigent-workspace/main.tf +++ b/registry/coder-labs/templates/omnigent-workspace/main.tf @@ -18,6 +18,21 @@ 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 tmux bubblewrap + ) 9>/tmp/coder-ai-tools-apt.lock + fi + EOT +} + resource "coder_agent" "main" { arch = data.coder_provisioner.me.arch os = "linux" @@ -66,16 +81,18 @@ module "codex" { source = "registry.coder.com/coder-labs/codex/coder" version = "5.0.0" - agent_id = coder_agent.main.id - enable_ai_gateway = true + 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 = ">= 4.0.0" - agent_id = coder_agent.main.id - enable_ai_gateway = true + agent_id = coder_agent.main.id + enable_ai_gateway = true + pre_install_script = local.ai_tools_pre_install_script } module "omnigent" { From eafdb27035ed865cebf253e3fbe8233d7d31e76b Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Tue, 23 Jun 2026 20:49:42 +0500 Subject: [PATCH 09/10] fix(coder-labs): install jq for Claude Code setup --- registry/coder-labs/templates/omnigent-workspace/README.md | 2 +- registry/coder-labs/templates/omnigent-workspace/main.tf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/registry/coder-labs/templates/omnigent-workspace/README.md b/registry/coder-labs/templates/omnigent-workspace/README.md index ab937c976..1e1355568 100644 --- a/registry/coder-labs/templates/omnigent-workspace/README.md +++ b/registry/coder-labs/templates/omnigent-workspace/README.md @@ -46,4 +46,4 @@ module "omnigent" { - Docker with `sysbox-runc` runtime installed on the Coder host - Coder Premium with AI Gateway enabled -The template installs `tmux` and `bubblewrap` before the AI tools start because Omnigent launches the Claude Code and Codex harnesses through local terminal sessions. +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 index d137af0e3..93cf5e111 100644 --- a/registry/coder-labs/templates/omnigent-workspace/main.tf +++ b/registry/coder-labs/templates/omnigent-workspace/main.tf @@ -27,7 +27,7 @@ locals { ( flock 9 sudo apt-get update - sudo apt-get install -y curl ca-certificates tmux bubblewrap + sudo apt-get install -y curl ca-certificates jq tmux bubblewrap ) 9>/tmp/coder-ai-tools-apt.lock fi EOT From 3e7972b286f383c75b2c94d8abd99827ccfa38f3 Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Tue, 23 Jun 2026 22:27:43 +0500 Subject: [PATCH 10/10] fix(coder-labs): use latest AI module versions --- registry/coder-labs/templates/omnigent-workspace/README.md | 4 ++-- registry/coder-labs/templates/omnigent-workspace/main.tf | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/registry/coder-labs/templates/omnigent-workspace/README.md b/registry/coder-labs/templates/omnigent-workspace/README.md index 1e1355568..8cef9480b 100644 --- a/registry/coder-labs/templates/omnigent-workspace/README.md +++ b/registry/coder-labs/templates/omnigent-workspace/README.md @@ -19,7 +19,7 @@ Each workspace runs its own isolated Omnigent server. The admin password is deri ```tf module "codex" { source = "registry.coder.com/coder-labs/codex/coder" - version = "5.0.0" + version = "5.2.0" agent_id = coder_agent.main.id enable_ai_gateway = true @@ -27,7 +27,7 @@ module "codex" { module "claude_code" { source = "registry.coder.com/coder/claude-code/coder" - version = ">= 4.0.0" + version = "5.2.0" agent_id = coder_agent.main.id enable_ai_gateway = true diff --git a/registry/coder-labs/templates/omnigent-workspace/main.tf b/registry/coder-labs/templates/omnigent-workspace/main.tf index 93cf5e111..a3727f068 100644 --- a/registry/coder-labs/templates/omnigent-workspace/main.tf +++ b/registry/coder-labs/templates/omnigent-workspace/main.tf @@ -79,7 +79,7 @@ resource "coder_agent" "main" { module "codex" { source = "registry.coder.com/coder-labs/codex/coder" - version = "5.0.0" + version = "5.2.0" agent_id = coder_agent.main.id enable_ai_gateway = true @@ -88,7 +88,7 @@ module "codex" { module "claude_code" { source = "registry.coder.com/coder/claude-code/coder" - version = ">= 4.0.0" + version = "5.2.0" agent_id = coder_agent.main.id enable_ai_gateway = true