Files
omnigent-ai--omnigent/README.md
T
Serena Ruan 54c5382a31 feat: Add Qwen Code as a harness (rebased + hardened #818) (#1020)
* feat(harness): add Qwen Code support

- Add qwen_executor.py: RPC-mode executor that spawns 'qwen --mode rpc'
  and communicates via JSONL protocol
- Add qwen_harness.py: FastAPI harness wrap mirroring claude-sdk/codex
- Register 'qwen' harness in _HARNESS_MODULES
- Add 'qwen-code' alias to HARNESS_ALIASES
- Include unit tests (test_qwen_executor.py) and e2e test
- Import order fixed to satisfy ruff E402/I001 rules

* feat(harness): add Qwen Code integration

This PR adds full Qwen Code support to Omnigent, mirroring the Kimi
integration pattern. The harness routes through OpenAI-compatible
providers and supports Databricks gateway authentication.

Changes:
- omnigent qwen CLI command with --resume support
- Spec validation for 'qwen' and 'qwen-code' harness identifiers
- Provider routing via HARNESS_QWEN_* env vars
- Databricks profile/model prefix detection
- Full integration with onboarding, runner, workflow, model layer

Files added:
- omnigent/qwen_native.py: Native Qwen wrapper for CLI
- docs/QWEN_FOLLOWUPS.md: Deferred work tracking

Tests updated:
- test_harness_install.py: Added qwen install spec test
- test_harness_readiness.py: Added expected_keys for qwen spellings
- test_provider_spawn_env.py: Added 2 tests for _build_qwen_spawn_env

Documentation:
- README.md: Added qwen to harness options comment
- AGENT_YAML_SPEC.md: Added Qwen section with examples

* test(qwen): expand test coverage and fix provider routing

- tests/inner/test_qwen_executor.py: Expand from 4 to 31 tests covering:
  * Registry/allowlist (OMNIGENT_HARNESSES, OMNIGENT_HARNESS_ALIASES)
  * FastAPI app shape (/health route present)
  * Env-var factory (HARNESS_QWEN_* → executor kwargs)
  * _build_argv (every flag passed to qwen)
  * Event translator (text_delta, tool_call, turn_complete, error)
  * run_turn end-to-end with stubbed subprocess
  * Missing-binary error path
  * Capability flags (handles_tools_internally, supports_streaming)
  * Session lifecycle and process termination

- omnigent/runtime/workflow.py: Add qwen to provider routing:
  * _PROVIDER_HARNESS_FAMILY: 'qwen': OPENAI_FAMILY
  * _HARNESS_GATEWAY_FLAG: 'qwen': 'HARNESS_QWEN_GATEWAY'
  * _QWEN_FAMILY_KEY: family key mapping for gateway base URLs

- tests/runtime/test_provider_spawn_env.py:
  * Add test_qwen_uses_openai_global_default
  * Add test_qwen_falls_back_to_catalog_default_model

* fix(qwen): resolve lint errors and test issues

- omnigent/qwen_native.py: Simplified to 99 lines from 324, matching kimi
  pattern using run.main(['--harness', 'qwen', *args]) instead of full
  native TUI launcher. Removed unused imports (asyncio, json, etc.)

- omnigent/cli.py: Fixed E501 line too long in _DEFAULT_HARNESS_PROMPTS

- omnigent/onboarding/harness_readiness.py: Refactored long condition
  to fix E501 error

- tests/inner/test_qwen_executor.py:
  * Removed unused imports (subprocess, sys)
  * Fixed test_tool_server_rejects_wrong_token with timeout handling
  * Simplified process_kill_on_timeout test to match actual behavior
  * Removed unused variable assignments in stubbed run_turn tests

* docs(qwen): add AgentCard.tsx comment and example

- ap-web/src/components/AgentCard.tsx: Add qwen to iconForAgent fallback
  logic (falls back to BotIcon like other non-native harnesses), update
  doc comments to document this behavior.

- examples/qwen_hello.yaml: Single-file launcher example for Qwen Code,
  mirroring the pattern of existing examples. Includes install instructions
  and provider configuration guidance.

* fix(qwen): resolve runtime crash and simplify implementation

- omnigent/qwen_native.py: Deleted entirely. The native TUI launcher
  was over-engineered (324 lines) with missing imports, unused variables,
  and dead code. Replaced with a simple 5-line forward to run.main.

- omnigent/cli.py: Simplified qwen command from 60 lines to 18 lines.
  Removed --server/--resume/--session options (not needed for headless
  harness). Now forwards all args directly to omnigent run --harness qwen.

- tests/cli/test_cli.py: Added test_qwen_command_forwards_to_run_main
  smoke test to catch this regression class in CI.

- tests/onboarding/test_harness_install.py: Fixed npm package name from
  @qwen/qwen-code to @qwen-code/qwen-code (verified on npm registry).

- ap-web/src/components/AgentCard.tsx: Removed dead code that checked
  agent.harness?.includes("qwen"). Added comment explaining qwen falls
  back to BotIcon for now.

- examples/qwen_hello.yaml: Fixed npm package name and simplified quick-start
  to use omnigent run instead of python -m omnigent.

* fix(qwen): rewrite QwenExecutor to use ACP (qwen --acp) protocol

The previous QwenExecutor was entirely broken against qwen v0.18+:

  1. Wrong launch flag: invoked 'qwen --mode rpc' which does not exist.
     The process exited immediately, causing EPIPE (Broken pipe) on the
     next write to stdin.

  2. Wrong protocol: the old executor spoke a custom JSONL dialect
     (session_start/text_delta/turn_complete) that qwen never implemented.

  3. Sync/async mismatch: called .drain() on a synchronous Popen
     TextIOWrapper which has no such attribute.

Fix: rewrite the executor to drive qwen via ACP (Agent Communication
Protocol), a JSON-RPC 2.0 protocol over newline-delimited stdin/stdout
launched with 'qwen --acp'. Session lifecycle:

  1. initialize   - one-time capability handshake per subprocess
  2. session/new  - create a session; use the server-assigned sessionId
                    (qwen may remap the client-proposed id)
  3. session/prompt - send user turn; consume streaming session/update
                      notifications (agent_message_chunk) and await the
                      final response with stopReason

The StreamReader limit is raised to 16 MiB to prevent the
'Separator is not found, and chunk exceed the limit' error on large
session/new responses (model lists etc).

Also fixes:
- Remove unused ToolCallRequest import in qwen_executor.py
- Fix stale 'RPC mode' comments in harnesses/__init__.py and e2e test
- Update docs/QWEN_FOLLOWUPS.md to reflect ACP instead of RPC mode
- Replace test_qwen_executor.py: old tests imported deleted _ToolServer
  and tested dead API. New tests cover construction, close() lifecycle,
  _rpc_id monotonicity, _read_stdout dispatch, _ensure_session server-ID
  handling, run_turn success/ACP-error/session-reset paths, and
  harness registry/alias wiring. All 22 tests pass.

Fixes #806

* fix(qwen): attachments, provider routing, permission gating, docs

- Forward attached files (fenced inline text) and images (real ACP image
  blocks when qwen advertises promptCapabilities.image); fixes weak models
  narrating tool calls as prose on file turns and dropped images.
- Add provider/gateway credential routing: translate HARNESS_QWEN_GATEWAY_*
  into OPENAI_BASE_URL/API_KEY/MODEL for the qwen subprocess (verified
  end-to-end vs an OpenAI-compatible gateway).
- Route session/request_permission through Omnigent's TOOL_CALL policy +
  elicitation; fix approval-event flattening and elicitation branding.
- Expand tests (executor, agent integration, gateway, wrap wiring);
  refactor QWEN_FOLLOWUPS by priority; remove examples/qwen_hello.yaml.

Co-authored-by: Isaac

* fix(qwen): address code-quality review nits + e2e drift guards on #1020

Code-quality bot nits:
- Comment the intentional empty except blocks in _read_stderr/_read_stdout
  (cancellation/EOF on shutdown is expected, not an error).
- Drop redundant local `import json` in _qwen_auth_configured (module-level
  json already imported).
- Remove dead `fake_readline_gen` helper in
  test_read_stdout_resolves_pending_future.
- Normalize test_cli.py to a single import style for omnigent.cli: import the
  qwen helpers directly and monkeypatch via string targets instead of
  `import omnigent.cli as c`.

E2E drift guards (CI shard 0/1 failures):
- Add qwen_perm_test to _ALT_COVERED in test_examples_coverage_sync.py
  (covered by tests/inner/test_qwen_agent_integration.py + the dedicated
  test_per_harness_qwen.py round-trip, not a test_example_<name>.py).
- Exclude qwen from test_run_harness_live_matrix_covers_registered_coding_harnesses:
  the qwen wrap routes via HARNESS_QWEN_GATEWAY_BASE_URL/AUTH_COMMAND rather
  than the shared HARNESS_<HARNESS>_GATEWAY probe wiring, so it can't ride the
  shared no-AGENT matrix; its live round-trip is covered by test_per_harness_qwen.py.

Co-authored-by: Isaac

* fix(qwen): remove unused constants flagged by code-quality on #1020

- qwen_executor.py: drop unused ACP method constants
  _AGENT_METHOD_SESSION_LOAD / _AGENT_METHOD_SESSION_CANCEL (only
  initialize/session.new/session.prompt are actually sent).
- qwen_harness.py: drop unused _TRUTHY_STRINGS (no _truthy parser here,
  unlike the sibling wraps it was copied from).
- workflow.py: drop vestigial _QWEN_FAMILY_KEY — it mapped families to a
  HARNESS_QWEN_GATEWAY_BASE_URLS (plural) object, but the qwen wrap routes
  via the singular HARNESS_QWEN_GATEWAY_BASE_URL + AUTH_COMMAND, so the map
  was never consulted.

Co-authored-by: Isaac

* fix(qwen): fix 3 ACP turn-loop correctness bugs in QwenExecutor

1. JSON-RPC id-namespace collision (CRITICAL): _read_stdout matched a
   message to a pending future by id alone. qwen mints its own request ids
   from a counter that can collide with ours, so a server-initiated request
   (e.g. session/request_permission) could resolve our prompt future with a
   request object — dropping the real response and hanging the turn. Now
   require "no method" before treating a message as a response.

2. Human-approval timeout (MAJOR): the turn deadline was absolute, but
   _respond_to_agent_request blocks synchronously on human elicitation. An
   approval slower than the remaining budget tripped a spurious timeout even
   though the user approved. The deadline is now idle-based — reset on every
   inbound message, including after the approval round-trip.

3. Chunk truncation race (MAJOR): the reader can enqueue several chunks and
   resolve the prompt future before run_turn drains the queue, so a bare
   fut.done() check returned with chunks still buffered. Completion is now
   gated on fut.done() AND an empty queue.

Adds regression tests for each (each fails on the pre-fix code).

Co-authored-by: Isaac

* fix(qwen): wake futures on stdout EOF + reset handshake on restart

Two crash-recovery correctness bugs in QwenExecutor:

- _read_stdout: a clean EOF (the normal manifestation of subprocess
  death) exited the reader without failing pending futures, so an
  in-flight session/prompt hung until the 300s idle timeout. Now fail
  pending futures with EOFError on EOF so run_turn fails fast.
- _start_process: _initialized is a one-way latch never reset on
  process death, so a restart after a crash skipped the ACP initialize
  handshake and qwen rejected the next session/new. Reset _initialized
  and _image_supported at the top of _start_process.

Also updates QWEN_FOLLOWUPS.md (OS sandbox under "What works today";
narrow the File I/O pending item to Omnigent-side execution/recording).

Co-authored-by: Isaac

---------

Co-authored-by: Ankush Bhatiya <ankushb@gmail.com>
2026-06-23 21:19:32 +08:00

16 KiB

Omnigent

The open-source AI agent framework and meta-harness for all your AI agents.

Omnigent is an open-source AI agent framework and meta-harness that gives you a common orchestration layer over Claude Code, Codex, Cursor, Pi, and the agents you write yourself: swap or combine harnesses without rewriting, enforce policies and sandboxing, and collaborate in real time from any device.

License: Apache 2.0 Status: alpha Python 3.12+

omnigent.ai · ⬇️ Download the macOS desktop app

An Omnigent orchestrator and its sub-agents in one shared session


Why Omnigent?

Omnigent lets you:

  • 📱 Work with agents from any device, including your phone. Sessions follow you: start in your terminal, continue in the browser, pick it up on your phone. Messages, sub-agents, terminals, and files stay in sync.

  • 🤖 Supervise multiple agents. Use Claude Code, Codex, Pi, and custom agents (defined in YAML) together in the same session. Ask one agent to review another's work, or split a task across agents that are each good at different things.

  • 🔌 Use any model. A first-party API key, a Claude/ChatGPT subscription, or any compatible gateway. All first-class.

  • 🤝 Collaborate. Share a session so teammates can chat with your agent and watch it work live, co-drive it on your machine, or fork the conversation to continue on their own.

  • ☁️ Run agents in cloud sandboxes. No laptop required: run sessions in disposable Modal, Daytona, or Islo sandboxes, launched from the CLI or provisioned by the server per session (managed hosts).

  • 🛡️ Govern your agents. Create policies to pause for your approval before risky actions, cap spend, or limit which tools an agent reaches. They apply to the whole server, one agent, or a single chat.


Quick start

1. Install

One command installs Omnigent and everything it needs:

curl -fsSL https://raw.githubusercontent.com/omnigent-ai/omnigent/main/scripts/install_oss.sh | sh
Prefer to install manually?

Omnigent needs Python 3.12+. Install the omnigent package:

uv tool install omnigent        # or: pip install "omnigent"

Or with Homebrew:

brew install omnigent-ai/tap/omnigent

Or install straight from the repo:

uv tool install -q --python 3.12 git+https://github.com/omnigent-ai/omnigent.git
Toolchain and prerequisites (if the installer reports a missing tool)
  • uv (required). https://docs.astral.sh/uv/getting-started/installation/ The installer offers to set this up for you.
  • git (required).
  • Node.js 22 LTS or newer with npm, for the Claude, Codex, and Pi coding harnesses. omnigent run installs the harness CLI you pick. https://docs.npmjs.com/downloading-and-installing-node-js-and-npm
  • tmux, required by the native omnigent claude / omnigent codex wrappers (brew install tmux / apt install tmux; the installer offers to install it for you).
  • bubblewrap (bwrap), Linux only. The native omnigent claude / omnigent codex and pi harnesses wrap each agent terminal in a bwrap OS-sandbox; on Linux that isolation is mandatory, so a missing bwrap binary makes those terminals fail to start (apt install bubblewrap; the installer offers to install it for you). macOS uses the built-in seatbelt sandbox and needs nothing extra.
  • Databricks (optional). To use a Databricks workspace as your model provider, install Omnigent with the databricks extra: uv tool install "omnigent[databricks]" — or pass it to the bootstrap installer with ... | sh -s -- --extra databricks. Signing in to the workspace also uses the Databricks CLI.
Updating to a new release

When a newer release is on PyPI, Omnigent shows a one-line notice (once per release) pointing here. To update:

omni upgrade            # detects how you installed, drains & stops the local
                        # server, then runs the matching upgrade command
omni upgrade --check    # just report whether a newer release is available

omni upgrade waits for in-flight agent sessions to finish before stopping the local server (pass --force to stop them immediately); the next omni command brings the server back up on the new version. Source checkouts update with git pull instead. Silence the notice with OMNIGENT_NO_UPDATE_CHECK=1.

The check queries your configured package index — honoring UV_INDEX_URL / PIP_INDEX_URL and your uv.toml / pip.conf (default PyPI), so private mirrors work out of the box; override with OMNIGENT_INDEX_URL if needed.

2. Start your first agent

omnigent picks a model with you and starts a session in your terminal. It also launches a local web UI at http://localhost:6767 that shows the same session in the browser, or on a phone on your network (step 4). The desktop app wraps that same UI in a native window and adds OS notifications and a dock badge — download it for macOS.

Note

The install puts two names for the same CLI on your PATH: omnigent and the shorter omni. They're interchangeable.

Tip

On first run, Omnigent picks up model credentials already in your environment (an ANTHROPIC_API_KEY / OPENAI_API_KEY, or a claude / codex CLI you're logged into) and offers one as the default.

omnigent

Or launch a specific agent runtime, or your own agent:

omnigent claude                      # Claude Code, in a session your team can join
omnigent codex                       # Codex
omnigent run path/to/agent.yaml      # your own agent (see "Write your own agent")

🐙 Polly and 🟠🔵 Debby

Two example agents ship with the repo, and they make good first sessions:

omnigent run examples/polly/
omnigent run examples/debby/

# Run an orchestrator on a different harness (sub-agents keep their own):
omnigent run examples/polly/ --harness pi
omnigent run examples/debby/ --harness openai-agents
omnigent run examples/polly/ --harness cursor  # Cursor CLI (needs cursor-agent + CURSOR_API_KEY)

🐙 Polly is a multi-agent coding orchestrator who writes no code herself. She's the tech lead: she plans, delegates the work to coding sub-agents (Claude Code, Codex, or Pi) in parallel git worktrees, then routes each diff to a reviewer from a different vendor than the one that wrote it. You merge.

🟠🔵 Debby is a brainstorming partner with two heads, one Claude and one GPT. Every question you ask goes to both heads, and she lays the two answers out side by side. Type /debate and the heads critique each other for a few rounds before converging. (She needs both a Claude and an OpenAI credential; see step 3.)

Prefer the browser? Start a server and register your machine as a host:

omnigent server start   # start the local server and web UI in the background
omnigent host           # (separate terminal) register this machine as a host

In the web UI, hit New Chat, pick your machine, and go. Check status with omnigent server status; stop everything with omnigent stop.

3. Choose & switch models

omnigent setup

Add a credential, set a default, or remove one, grouped by agent. Omnigent works with four kinds of credentials:

Kind What it is
🔑 API key A first-party vendor key for Anthropic, OpenAI, and similar providers
🎟️ Subscription A Claude Pro/Max or ChatGPT plan, via the official claude / codex CLIs
🌐 Gateway Any OpenAI- or Anthropic-compatible base_url and key (OpenRouter, LiteLLM, Ollama, vLLM, Azure)
🧱 Databricks A Databricks workspace profile (requires the databricks extra)

Defaults are per agent, so a Claude default and a Codex default coexist. You can also switch models in the middle of a session with the /model command.

Gateway base URLs (OpenRouter, Ollama)

When you add a Gateway credential, omnigent setup asks for a base URL and a key. The base URL depends on which agent you point it at:

Provider For Base URL Key
OpenRouter Claude Code https://openrouter.ai/api your OpenRouter key (sk-or-…)
OpenRouter Codex / OpenAI agents https://openrouter.ai/api/v1 your OpenRouter key (sk-or-…)
Ollama (local) Codex / OpenAI agents http://localhost:11434/v1 any value (Ollama ignores it)

For Claude Code, point at OpenRouter's Anthropic-compatible endpoint (…/api, not …/api/v1). For Codex and the OpenAI-agents harness, use the OpenAI-compatible …/api/v1.

4. Deploy a server (and use it from your phone📱)

Run Omnigent on a server with a stable URL (deploy/README.md is the full guide) and your sessions become reachable from anywhere, including your phone. The web UI is built for mobile, so you get the same chat, sub-agents, terminals, and files, in sync with your laptop.

One docker compose up runs the server on any host you have (a VPS, a home server); Render deploys with one click; Fly.io, Railway, Hugging Face Spaces, and Modal are covered too. The server can also provision a cloud sandbox per session (managed hosts), so no laptop has to stay online. The full menu of targets, the database options, and the sandbox setup live in deploy/README.md.

Once the server is up, sign in and register your laptop as a host:

omnigent login https://your-host    # sign in once; run / attach / host reuse the token
omnigent host  https://your-host    # new sessions can now run on this machine

Tip

On your own network you don't need a deploy. Open your machine's LAN address on your phone (e.g. http://192.168.x.x:6767).

5. Collaborate with your team

Omnigent supports multi-user accounts, controlled by one environment variable:

OMNIGENT_AUTH_ENABLED=1 omnigent server start

The Docker deploy in step 4 turns it on for you (OMNIGENT_AUTH_ENABLED defaults to 1 there).

Invite your teammates

Open the web UI (http://localhost:6767 locally, or your host's URL) and sign in as admin; first run prints the password and saves it locally. Then open Admin → Members → Invite to create a single-use invite link, no email server needed. Send it over; your teammate opens it, sets a password, and they're in. Signup is invite-only.

Note

Teammates need to be able to reach the server. A local server is only reachable on your network; for anyone off it, deploy an always-on host (see step 4).

Code together

  • Share a live session. Hit Share in the web UI and send the link; teammates watch your agent work and chat with it in real time.

  • Co-drive. A teammate co-attaches to your running session; their messages execute on your machine. Great for pairing or handing the keyboard to a domain expert mid-investigation.

    omnigent attach <session_id>
    
  • Fork. Clone a conversation onto your own machine and continue independently from the fork point.

    omnigent run --fork <session_id>
    

Tip

Want your team to sign in with the logins they already have (Google, GitHub, Okta, Microsoft)? Set OMNIGENT_OIDC_ISSUER plus a client ID and secret on your deployed server and restart. The full walkthrough, domain allowlists, and the proxy-only header auth mode are covered in deploy/README.md#auth.

6. Govern your agents with policies

Policies decide what an agent may do: run shell commands, edit files, spend tokens. They check every action and either allow it, block it, or pause to ask you first.

  • In the web UI: open a session's info panel to browse the available policies and toggle them on or off.
  • In chat: ask. "Add a policy that asks me before running shell commands." The agent sets it up for you.

Want defaults that apply to everyone, or to a specific agent? Define them in your server config or an agent's YAML:

policies:
  approve_shell:
    type: function
    handler: omnigent.policies.builtins.safety.ask_on_os_tools   # ask before shell / file writes
  cap_calls:
    type: function
    handler: omnigent.policies.builtins.safety.max_tool_calls_per_session
    factory_params:
      limit: 50                    # cap how many tools one session can call
  budget:
    type: function
    handler: omnigent.policies.builtins.cost.cost_budget
    factory_params:
      max_cost_usd: 5.00           # hard spend cap...
      ask_thresholds_usd: [3.00]   # ...with a soft warning on the way

Policies stack across three levels, server-wide (admin), per-agent (developer), and per-session (you), with the stricter session rules checked first. Spend caps and access limits ship as builtins.

See the policy guide for the full catalog and trust model.


Write your own agent

An agent is a short YAML file: your prompt, your tools, and optional helper sub-agents a supervisor can delegate to. You don't have to write it by hand: agents can build agents, so describe the agent you want in any Omnigent chat and it authors the file for you.

name: my_agent
prompt: You are a helpful data analyst.

executor:
  harness: claude-sdk          # or: claude-native, codex, codex-native, cursor, cursor-native, openai-agents, pi, pi-native, antigravity, qwen

tools:
  # A local Python function (schema auto-generated from the signature)
  word_count:
    type: function
    callable: mypackage.mymodule.word_count

  # A sub-agent the supervisor can delegate to
  researcher:
    type: agent
    prompt: Search for relevant information and summarize it.
    tools:
      word_count: inherit

Run it with:

omnigent run path/to/my_agent.yaml

The same file can declare sub-agents and reviewers. For a fuller example, see Polly at examples/polly/, and the Agent YAML spec for the full schema.


Contributing

Contributions are welcome. See CONTRIBUTING.md for how to set up your environment, run the checks, and open a pull request.

Contributors

Thanks to all of our amazing contributors!