Files
omnigent-ai--omnigent/deploy/docker/README.md
T
Michael Gardner 6f0257dbc7 feat(kiro): add native CLI harness (#899)
* feat: add Kiro native CLI harness

Signed-off-by: Michael Gardner <gardnmi@gmail.com>

* fix(kiro): avoid ambient env in tmux attach

Signed-off-by: Michael Gardner <gardnmi@users.noreply.github.com>

* fix: restore uv.lock pypi.org sources (drop accidental databricks-proxy re-lock)

A local `uv run` during the merge re-locked uv.lock against this machine's
Databricks-internal pypi proxy, flipping every package source URL. Kiro changes
no dependencies and pyproject.toml is unchanged vs main, so restore main's
uv.lock verbatim (pypi.org sources). Only registry URLs differed — no version
or hash changes.

Co-authored-by: Isaac

* test(e2e-ui): add native-kiro render-parity suite (E2E UI Required gate)

The E2E UI Required gate flagged that #899 changes the agent-picker/session UI
(adds Kiro) without a tests/e2e_ui/** test. Add test_native_kiro_render_parity.py
mirroring the cursor/goose siblings — composer-IN parity, a TUI-originated turn
surfacing OUT, and no duplicate rendering — plus the native_kiro_session fixture.
Skip-gated on kiro-cli + tmux, so it skips in CI (no Kiro account provisioned)
exactly like the goose/cursor suites, and runs for real where Kiro is signed in.

Verified: collects + skips cleanly (kiro-cli absent); ruff clean.

Co-authored-by: Isaac

* fix: restore ap-web/package-lock.json npmjs.org sources (drop databricks npm-proxy)

Same root cause as the uv.lock fix: an npm command during round-1 merge re-resolved
one dependency (yaml-1.10.3) against this machine's Databricks-internal npm proxy
(npm-proxy.cloud.databricks.com), which CI (pinned to registry.npmjs.org) can't reach
-> 'npm ci' ETIMEDOUT. ap-web/package.json is unchanged vs main and Kiro adds no npm
dependency, so restore main's package-lock.json verbatim (clean npmjs.org sources).

Co-authored-by: Isaac

* test(e2e): exclude kiro-native from the live-harness matrix coverage check

test_run_harness_live_matrix_covers_registered_coding_harnesses asserts every
registered coding harness is either in the live no-AGENT e2e matrix or explicitly
excluded. kiro-native is a terminal-first TUI launched via `omni kiro` (tmux pane
+ bridge dir), not `omnigent run --harness kiro-native`, so — like goose-native /
qwen-native / cursor-native — it can't run in this matrix. Add it to the exclusion
set with the matching rationale; its coverage is the kiro-native bridge/executor/
forwarder unit tests + the test_native_kiro_render_parity e2e_ui suite.

Co-authored-by: Isaac

* test(ap-web): set isNativeWrapper in /compact composer menu tests

#1139 gated "/compact" behind isNativeWrapper (hidden for non-native
harnesses), but the three slash-menu-UX tests that assert "/compact"
tops/appears in the suggestions still rendered a non-native composer,
so they now fail on main (and on every PR that merges main).

Render those three with isNativeWrapper:true so "/compact" is offered,
restoring the built-in ordering the tests pin. Test-only; no behavior
change. Fixes the inherited ChatPage.composer.test.tsx red on this PR.

Co-authored-by: Isaac

* test(kiro): cover kiro_native launcher helpers (raise coverage 43%→70%)

The kiro-native launcher (omnigent/kiro_native.py) was the largest
coverage gap on this PR: its CLI/daemon orchestration is only exercised
by the live render-parity e2e, which skips in CI when kiro-cli is
absent. Add focused unit tests (with a fake httpx client) for the
unit-testable surface: executable resolution, launch-argv assembly,
terminal-payload decoding, tmux attach gating, startup-progress
forwarding, preflight, resume-id resolution, and the create/fetch/
ensure/find/wait session helpers (success + error branches).

Lifts kiro_native.py from 43% to 70%; remaining misses are the
daemon-driven async orchestration covered by runner/e2e paths.

Co-authored-by: Isaac

* test(kiro): rename test env var to avoid exfil-scan false positive

The CI exfil scanner flags any added file containing a secret-named
source (regex `[A-Z0-9]+_SECRET\b`) together with a network sink. The
tmux-allowlist test used `OMNIGENT_SECRET` purely as a non-allowlisted
sample var, which matched the secret regex and — combined with the
fake httpx client's .post()/.get() in the same file — tripped the
"secret-named source + network sink" block. Rename it to a neutral
`OMNIGENT_UNLISTED_VAR`; the test's intent (filtering non-allowlisted
keys) is unchanged.

Co-authored-by: Isaac

---------

Signed-off-by: Michael Gardner <gardnmi@gmail.com>
Signed-off-by: Michael Gardner <gardnmi@users.noreply.github.com>
Co-authored-by: Pat Sukprasert <pattara.sk127@gmail.com>
2026-06-25 00:55:25 +00:00

12 KiB

Omnigent — docker-compose stack

Run the server as a self-contained Docker stack on any host: your laptop, a VPS, an EC2 instance, a home server, anywhere docker compose runs.

The stack:

  • postgres — persistent DB on a Docker volume
  • omnigent — the server image (built from ../Dockerfile)

Auth is in-process — the server has both header-proxy and native OIDC modes built in (see Multi-user mode below). There is no separate auth-proxy container.

Quickstart (single-user)

cd deploy/docker
./bootstrap.sh                          # mints POSTGRES_PASSWORD + cookie secret into .env
docker compose up -d
docker compose logs -f omnigent       # ctrl-c when boot is clean

bootstrap.sh is idempotent — re-running it leaves already-set secrets alone. If you prefer to manage .env yourself, just cp .env.example .env and edit POSTGRES_PASSWORD (and OMNIGENT_OIDC_COOKIE_SECRET if you're enabling OIDC) by hand.

Server is on http://localhost:8000. The web UI prints the CLI command to launch a local runner against it. From your laptop:

omnigent run path/to/agent.yaml --server http://localhost:8000

Reset everything (drops the DB and the artifact store):

docker compose down -v

Multi-user mode (accounts — default)

Built-in accounts auth: no IdP to register, no proxy to host. This is the default — docker compose up -d brings it up with no extra env wiring. First boot creates an admin user (named after the operator's OS user, falling back to admin in headless containers) with a random password that lands in the container logs and on the persistent volume at /data/admin-credentials.

For any deploy reachable through a public domain, also set the external URL so invite links resolve correctly:

# Add to .env (bootstrap.sh already minted the cookie secret for you):
OMNIGENT_ACCOUNTS_BASE_URL=https://omnigent.example.com

docker compose up -d
docker compose logs omnigent | grep -A4 "Created initial admin"

Copy the random password from the log line into the web UI's login form, then:

  • Click your username in the top-right → MembersInvite member.
  • Share the single-use URL with the teammate; they pick their own username and password when they redeem it.
  • Sign-out lives in the same account menu.

Headless deploy (CI, Cloud Run, etc.) where you can't read the logs? Pre-seed the password:

OMNIGENT_ACCOUNTS_INIT_ADMIN_PASSWORD=<your-strong-password>

The persistent password file is at /data/admin-credentials on the artifact-data volume — survives docker compose restart, deleted by docker compose down -v.

Multi-user mode (OIDC)

Single-user mode trusts everyone who reaches the port and uses the identity "local" for all requests. For a shared deploy, the server has native OIDC support — it handles the full login flow itself (/auth/login, /auth/callback, /auth/logout) with a signed session cookie. No extra container, no Caddy basic-auth shim, no oauth2-proxy.

Walkthrough: GitHub OAuth (easiest to register)

  1. Register the OAuth app. Go to https://github.com/settings/developers → New OAuth App. Set the callback to https://<your-host>/auth/callback (HTTPS is strongly recommended; GitHub permits HTTP for testing but warns).

  2. Mint a cookie secret. ./bootstrap.sh already did this on the quickstart path — OMNIGENT_OIDC_COOKIE_SECRET is set in your .env. If you skipped it, run openssl rand -hex 32 and paste the value yourself.

  3. Edit .env:

    OMNIGENT_AUTH_PROVIDER=oidc
    OMNIGENT_OIDC_ISSUER=https://github.com
    OMNIGENT_OIDC_CLIENT_ID=Iv1.abc123…
    OMNIGENT_OIDC_CLIENT_SECRET=OMNIGENT_OIDC_REDIRECT_URI=https://omnigent.example.com/auth/callback
    # OMNIGENT_OIDC_COOKIE_SECRET is already set by bootstrap.sh — leave it alone.
    
  4. Bring it up.

    docker compose up -d
    

    The server will fail loud at startup if any required OIDC env var is missing — check docker compose logs omnigent if it doesn't come up.

  5. Visit the URL → you should be redirected to GitHub to log in, then back to the web UI with a __Host-ap_session cookie set.

Walkthrough: Google Workspace (with domain allowlist)

OMNIGENT_AUTH_PROVIDER=oidc
OMNIGENT_OIDC_ISSUER=https://accounts.google.com
OMNIGENT_OIDC_CLIENT_ID=…apps.googleusercontent.com
OMNIGENT_OIDC_CLIENT_SECRET=OMNIGENT_OIDC_REDIRECT_URI=https://omnigent.example.com/auth/callback
OMNIGENT_OIDC_COOKIE_SECRET=<64-hex-chars>
OMNIGENT_OIDC_ALLOWED_DOMAINS=example.com,subsidiary.example.com

ALLOWED_DOMAINS is critical when the OAuth consent screen is "External" — without it, any Google account on the planet can log in.

Generic OIDC (Okta, Auth0, Keycloak, Entra ID)

Any IdP that publishes /.well-known/openid-configuration works. Set OMNIGENT_OIDC_ISSUER to the base URL; the server fetches discovery at startup.

OMNIGENT_AUTH_PROVIDER=oidc
OMNIGENT_OIDC_ISSUER=https://your-tenant.okta.com
OMNIGENT_OIDC_CLIENT_ID=OMNIGENT_OIDC_CLIENT_SECRET=OMNIGENT_OIDC_REDIRECT_URI=https://omnigent.example.com/auth/callback
OMNIGENT_OIDC_COOKIE_SECRET=<64-hex-chars>

HTTPS for the callback URL

Most IdPs require HTTPS for non-localhost redirect URIs, and the session cookie uses the __Host- prefix which browsers only accept over HTTPS. Three options:

  1. Use the bundled Caddy overlay (easiest — any VPS / EC2 / home server with a public domain):

    # In .env:
    OMNIGENT_DOMAIN=omnigent.example.com
    OMNIGENT_ACME_EMAIL=you@example.com      # optional, for Let's Encrypt notices
    
    # Point DNS A/AAAA records at the host, then:
    docker compose -f docker-compose.yaml -f docker-compose.https.yaml up -d
    

    Caddy auto-provisions and renews a Let's Encrypt cert; the omnigent container stops being directly exposed and only :80 + :443 are published. Requires Docker Compose 2.24+ for the overlay's !reset directive. See Caddyfile for the (3-line) config.

  2. Behind an existing reverse proxy — point your proxy at omnigent:8000 over the docker network (or 127.0.0.1:8000 from the host). Examples: AWS ALB with ACM cert, Cloudflare in "Full" SSL mode, Fly.io / Cloud Run / Render platform certs.

Header-proxy mode (for deploys behind an existing SSO proxy)

If you already have oauth2-proxy, Databricks Apps, AWS ALB OIDC, Cloudflare Access, Tailscale Funnel, or any other proxy that injects an identity header, set OMNIGENT_AUTH_PROVIDER=header. The server will reject requests without the header.

OMNIGENT_AUTH_PROVIDER=header

The header read is X-Forwarded-Email by default. Proxies that use a different header name set OMNIGENT_AUTH_HEADER to point the server at it — for example, Cloudflare Access supplies the authenticated email in Cf-Access-Authenticated-User-Email:

OMNIGENT_AUTH_PROVIDER=header
OMNIGENT_AUTH_HEADER=Cf-Access-Authenticated-User-Email

Some proxies namespace the value they inject. Google IAP forwards the email in X-Goog-Authenticated-User-Email prefixed with accounts.google.com:; set OMNIGENT_AUTH_HEADER_STRIP_PREFIX to drop it and recover the bare email:

OMNIGENT_AUTH_PROVIDER=header
OMNIGENT_AUTH_HEADER=X-Goog-Authenticated-User-Email
OMNIGENT_AUTH_HEADER_STRIP_PREFIX=accounts.google.com:

Security note: in this mode the proxy is responsible for stripping any inbound copy of the identity header from the client request — otherwise any visitor can spoof an identity. The server trusts whatever value reaches it.

Environment variables

Variable Default Purpose
POSTGRES_PASSWORD required DB password for the bundled Postgres container.
POSTGRES_USER / POSTGRES_DB omnigent DB user + database name.
OMNIGENT_PORT 8000 Host port the server is published on.
OMNIGENT_AUTH_ENABLED 1 (in compose) Master auth switch. 1 → accounts (or oidc if OMNIGENT_OIDC_ISSUER is set); 0 → single-user local mode (every request is the shared local user — local dev only, never shared deploys).
OMNIGENT_AUTH_PROVIDER unset Escape hatch to pin a mode explicitly: header / accounts / oidc. Overrides the AUTH_ENABLED auto-selection.
OMNIGENT_AUTH_HEADER X-Forwarded-Email Header-mode only: name of the trusted identity header. Set for proxies that use another name, e.g. Cf-Access-Authenticated-User-Email (Cloudflare Access).
OMNIGENT_AUTH_HEADER_STRIP_PREFIX unset (strip nothing) Header-mode only: prefix removed from the identity header value. Set to accounts.google.com: for Google IAP's X-Goog-Authenticated-User-Email.
OMNIGENT_OIDC_* unset OIDC config — required in oidc mode (issuer set, or AUTH_PROVIDER=oidc). See .env.example.
PYPI_INDEX_URL https://pypi.org/simple Build-time PyPI index — override only behind a corporate proxy.

DATABASE_URL and ARTIFACT_DIR are computed by compose and injected into the container.

Host image (--target host)

The same Dockerfile publishes a second image: the official Omnigent host image, which remote sandboxes boot from so they start in seconds instead of paying an in-sandbox dependency install. It bakes the full omnigent install (all three packages + deps, python and pip on PATH), git (workspaces / worktrees), tmux (terminal sessions spawned by native harnesses), and the coding-harness CLIs — claude, codex, pi, and kiro-cli, with the runtime they need — so claude-sdk / claude-native / codex / pi / kiro-native agents run in sandboxes without an in-sandbox install. None of the server-only bits are included (no SPA bundle, no psycopg, no uvicorn entrypoint).

CI publishes it next to the server image, with the same tag scheme:

  • ghcr.io/omnigent-ai/omnigent-host:latest — tracks main HEAD (the default for omnigent sandbox create --provider modal)
  • ghcr.io/omnigent-ai/omnigent-host:sha-<short> — immutable per-commit pin
  • ghcr.io/omnigent-ai/omnigent-host:vX.Y.Z — release tags

Build it locally from the repo root:

docker build -t omnigent-host:latest --target host \
             -f deploy/docker/Dockerfile .

Using it with the Modal sandbox provider

omnigent sandbox create --provider modal boots sandboxes from ghcr.io/omnigent-ai/omnigent-host:latest by default. Your local checkout's wheels are still built and overlaid on top at create time (pip install --force-reinstall --no-deps), so the sandbox runs exactly your code — the baked image just supplies the dependency tree. A checkout that adds a brand-new dependency needs that package installed manually in the sandbox until the official image rebuilds with it.

Two environment variables tune the pull:

Variable Purpose
OMNIGENT_MODAL_HOST_IMAGE Override the image ref, e.g. an org-internal copy (ghcr.io/<your-org>/omnigent-host:latest) or a :sha-<short> pin.
OMNIGENT_MODAL_REGISTRY_SECRET Name of a Modal secret holding registry credentials for private pulls. Create it with keys REGISTRY_USERNAME (your registry username) and REGISTRY_PASSWORD (for GHCR: a personal access token with read:packages). Unset = anonymous pull.

Using it with the Daytona sandbox provider

The same host image backs Daytona-managed sessions (server config sandbox.provider: daytona; Daytona is managed-only — there is no omnigent sandbox create --provider daytona CLI flow). Daytona ingests the registry image into an internal snapshot on first use (the first launch from a given image takes minutes; later launches reuse the snapshot and take seconds). Override the ref with OMNIGENT_DAYTONA_HOST_IMAGE or the server config's sandbox.daytona.image. See deploy/daytona/README.md for the full provider guide (credentials, the free-tier egress relay, and security considerations).

  • designs/OIDC_AUTH.md — full native OIDC design
  • designs/SESSIONS_AUTH.mdAuthProvider contract + permission system