7.9 KiB
Harness Plugin Interface
Omnigent now discovers optional harness support through Python entry points.
Core omnigent ships the built-in harness contribution. A separate package, for
example omnigent-kimi, can add harness ids, aliases, runner modules, install
metadata, model environment plumbing, and picker labels without adding that
harness to the default install.
The goal is:
pip install omnigentgives only core harnesses.pip install omnigent-kimiadds Kimi support to the sameomniCLI and server process.- Core can still produce a targeted error for known optional harness ids:
install
omnigent-kimi.
Package Contract
An optional harness package declares an entry point in the
omnigent.community.harness group. Community harness implementation modules
must also live under the omnigent.community.harness.* namespace; core rejects
plugins that try to register flat packages or override builtin harness names.
[project]
name = "omnigent-foo"
dependencies = [
"omnigent==0.3.0.dev0",
]
[project.entry-points."omnigent.community.harness"]
foo = "omnigent.community.harness.foo.plugin:get_contribution"
For local sibling checkouts, keep the package dependency normal and point uv at the local core checkout:
[tool.uv.sources]
omnigent = { path = "../omnigent-oss-2", editable = true }
If the plugin lives inside the core repo, the relative path should point back to
the repo root. If it moves to a sibling repo, update the path. A bad path is why
uv may try to build omnigent @ file:///Users/<user>.
Registry Types
The public interface lives in omnigent.harness_plugins:
from omnigent.harness_plugins import HarnessContribution
from omnigent.harness_install_spec import HarnessInstallSpec
HarnessInstallSpec intentionally lives outside omnigent.onboarding so a
plugin can be imported during entry-point discovery without pulling in the
provider/onboarding stack and creating import cycles.
HarnessContribution
Each plugin exports a get_contribution() function returning
HarnessContribution.
def get_contribution() -> HarnessContribution:
return HarnessContribution(
name="omnigent-foo",
valid_harnesses=frozenset({"foo"}),
harness_modules={
"foo": "omnigent.community.harness.foo.inner.foo_harness",
},
aliases={
"foo-code": "foo",
},
install_specs={
"foo": HarnessInstallSpec(
"Foo",
"foo",
package=None,
install_hint="curl -fsSL https://foo.example/install.sh | bash",
login_args=("login",),
logout_args=("logout",),
),
},
harness_install_keys={
"foo": "foo",
"foo-code": "foo",
},
missing_install_package={
"foo": "omnigent-foo",
"foo-code": "omnigent-foo",
},
harness_labels={"foo": "Foo"},
)
Field Semantics
valid_harnesses- Canonical harness ids accepted by spec validation once the plugin is installed.
harness_modules- Maps each canonical harness id to the subprocess module that creates the
harness app.
omnigent.runtime.harnessesmerges these into_HARNESS_MODULES. aliases- User-facing spellings canonicalized by
omnigent.harness_aliases, for examplefoo-code -> foo. install_specs- Plugin-provided CLI install/auth metadata, keyed by install key. Use
HarnessInstallSpecfromomnigent.harness_install_spec. harness_install_keys- Maps harness ids and aliases to an
install_specskey. Readiness and preflight checks use this to decide which CLI binary a harness requires. model_env_keys- Maps harness id to an env var name used by launcher/spec generation for model override plumbing.
spawn_env_builders- Maps headless harness id to a callable import path. The runner calls this to build per-spawn environment variables from the agent spec.
missing_install_package- Maps known optional harness spellings to the package that provides them. Core
uses this even when the plugin is not installed so validation and process-manager
errors can say
pip install omnigent-foo. harness_labels- Maps canonical harness ids to display labels returned by
GET /v1/harnessesand merged into web picker surfaces.
Runtime Flow
- Python loads installed entry points in
omnigent.community.harness. omnigent.harness_plugins.plugin_state()merges the built-in contribution with each plugin contribution.- Spec validation checks
accepted_harnesses()and usesmissing_install_package()for known optional harness hints. omnigent.runtime.harnessesregistersharness_modules().- Runner launch paths consult
spawn_env_builders()for contributed headless harnesses. - Host readiness uses
harness_install_keys()andinstall_specs()to gate CLI-backed contributed harnesses on their binary. - The server exposes
GET /v1/harnessesfromharness_catalog(). - The web UI merges
/v1/harnessesinto harness picker surfaces.
Minimal Headless Harness Checklist
For a non-native harness:
- Create a separate package, for example
omnigent-foo. - Add the
omnigent.community.harnessentry point. - Implement
get_contribution(). - Fill
valid_harnesses,harness_modules, andaliases. - Add
install_specsandharness_install_keysif the harness needs a CLI. - Add
spawn_env_buildersif the harness needs spec-derived env vars. - Add
missing_install_packageentries in core if the harness id should produce a targeted install hint before the plugin is installed. - Move harness implementation modules into the plugin package.
- Remove the harness id and module from the built-in contribution.
Native TUI Harnesses
Community native terminal harnesses are not supported by this interface yet.
Core native harnesses still use internal registry metadata, but the runner,
chat-resume, CLI-command, interrupt/stop, and built-in agent seeding paths are
not pluggable. Community plugins that set native_harnesses or native_agents
are rejected at load time until those lifecycle hooks are wired end to end.
Import Rules
Entry-point loading happens early and can happen while other core modules are
still initializing. Plugin plugin.py should keep top-level imports light:
- safe:
omnigent.harness_plugins,omnigent.harness_install_spec, constants, stdlib; - risky:
omnigent.onboarding.*,omnigent.cli, server modules, runner modules, or anything that importsomnigent.harness_aliases.
Put heavy imports inside the callable that needs them. For example, a spawn-env
builder may import provider/runtime helpers inside build_spawn_env(), but
get_contribution() should not need onboarding.
Local Demo Commands
Sibling checkout demo:
cd /path/to/omnigent-oss-2
uv pip install -e .
uv pip install -e ../omnigent-foo
uv run python -c "from omnigent.harness_plugins import valid_harnesses; print('foo' in valid_harnesses())"
uv run python -c "from omnigent.runtime.harnesses import _HARNESS_MODULES; print(_HARNESS_MODULES['foo'])"
If the plugin dependency still points at a published or wrong local omnigent,
use the sibling source override in the plugin pyproject.toml:
[tool.uv.sources]
omnigent = { path = "../omnigent-oss-2", editable = true }
For published packages, remove local source overrides and publish both distributions with compatible versions.
Tests To Add For Each Split Harness
- Core registry excludes the optional harness by default.
- Core validation/error messages suggest the optional package.
- Installing or faking the entry point adds
valid_harnesses, aliases, install specs, and harness modules. - Readiness/setup tests isolate core-only behavior by stubbing entry-point discovery when the optional package is installed in the dev environment.
- Two community plugins cannot claim the same harness spelling, alias, or install key.