Harness Standard — how an agentic program plugs into OpenProgram#
This document is the contract every harness (a self-contained agentic program shipped as its own repo) satisfies, so that cloning it into OpenProgram's
agentics/folder makes it auto-detected and usable with no host edits. The three first-party harnesses (GUI / Research / Wiki) are the reference implementations; third parties follow the same rules. Related:../installing-harnesses.md(install procedure),openprogram/functions/_registry.py(the loader),openprogram/functions/_programs.py(the first-party harness list).
0. The one rule that matters#
A harness is anything you drop into <openprogram>/functions/agentics/
that exposes its functions through <pkg>/agentics/__init__.py. The host
walks that folder on startup (and, with hot-reload, whenever it changes),
finds the inner package, imports <pkg>.agentics, and the
@agentic_function decorators self-register. Nothing else is required and
no host file is edited.
<Harness-Repo>/ ← what you git clone into agentics/
└── <pkg>/ ← importable package; folder name == import name
├── __init__.py ← marks it a package (may re-export niceties)
└── agentics/
└── __init__.py ← THE ENTRY POINT — exposes AGENTIC_FUNCTIONS
If a cloned folder doesn't match this shape, the host silently ignores it (a non-harness folder must never break the load). So "auto-detect" == "matches this contract".
1. The entry point: <pkg>/agentics/__init__.py#
Must define AGENTIC_FUNCTIONS — a list of @agentic_function-decorated
callables. Importing this module fires the decorators, which register the
functions into the shared tool registry. The list is also the harness's
declared public surface.
Required pattern — degrade gracefully, never crash the host:
# <pkg>/agentics/__init__.py
"""Entry point for the Foo harness."""
try:
from .foo_agent import foo_agent, foo_helper
AGENTIC_FUNCTIONS = [foo_agent, foo_helper]
except Exception: # missing optional dep, unsupported platform, …
# The host imports this module during discovery. If the harness can't
# load on this machine (a heavy dep isn't installed, this OS has no
# backend, …), expose NOTHING rather than raising — "can run → listed,
# can't → skipped", per the discovery contract. The host logs the
# cause under OPENPROGRAM_DEBUG_REGISTRY=1.
AGENTIC_FUNCTIONS = []
Rules:
AGENTIC_FUNCTIONSmust always be defined (even if[]).- The
try/exceptis mandatory — a harness that can't run on this machine must yield[], not propagate anImportError. This is what makes "clone anything in, the ones that can run light up, the rest stay dark" work. - Don't do heavy work at import time (no model downloads, no network, no GUI grab). Import must be cheap and side-effect-free beyond registration.
2. The package: <pkg>/#
- Folder name == import name.
gui_harness/is imported asgui_harness. The host puts the harness root onsys.pathso the harness's own absolute imports (from gui_harness.x import y) resolve. A hyphenated repo folder (GUI-Agent-Harness/) is fine — the host finds the inner ascii-identifier package inside it. - May vendor sibling packages (e.g. GUI harness ships
desktop_env/alongsidegui_harness/). The host picks the one package that has anagentics/sub-package as the entry; vendored siblings are ignored by discovery and just ride along onsys.path.
3. Configuration — the unified convention#
Harnesses differ wildly in what they configure (Research: providers + work dir; Wiki: a vault path; GUI: vision model + platform backend). The standard is the mechanism, not the parameters:
- Per-call parameters first. Anything that varies per invocation is a
function argument with a sane default:
@agentic_function(name="foo_agent") def foo_agent(task: str, max_steps: int = 15, runtime=None) -> dict: ... - The runtime is injected, never constructed. A harness function that
needs an LLM declares a
runtimeparameter; the host injects the active runtime. Harnesses must not build their own provider / readANTHROPIC_API_KEYthemselves for the in-host path — provider selection and auth belong to the host. (A harness MAY keep a standalone CLI that callscreate_runtime(...)for use outside the host; that's separate from the in-host entry point.) - Environment variables for machine-level settings, namespaced with
the harness's prefix, documented in the README:
WAH_VAULT # Wiki: vault root GUI_AGENT_MEMORY # GUI: learned-component store - No config files required to start. A harness must work with zero config (sensible defaults); config is opt-in override, never a setup gate. No mandatory wizard / first-run prompt.
- State / scratch goes under the host state dir, not a hand-rolled
home path. Use
openprogram.paths.get_state_dir()(→~/.openprogram/) as the base; never hardcode~/.agentic/...(that path is retired). A per-harness subdir is the convention:get_state_dir() / "harnesses" / "<pkg>".
4. Dependencies#
- Do NOT declare
openprogramas a git dependency. The host is the thing importing the harness, so it's already installed. Aopenprogram @ git+https://...line in the harness'spyproject.tomlcausespip install <harness>to re-fetch and possibly downgrade the host. List openprogram only as a documented assumption ("install into an environment that already has openprogram"), not a hard dep. - Declare the harness's own third-party deps (torch, Jinja2, …) in its
pyproject.toml/requirements.txt. Installing those is the harness's install step (run when the user installs the harness), not OpenProgram's concern — the host never auto-installs harness deps. - Heavy / native deps go behind an extra so a light clone stays light:
[project.optional-dependencies] ocr = ["easyocr"] - The graceful-degrade
try/exceptin §1 is what lets a harness be cloned before its deps are installed without breaking the host — it just stays dark until the deps are present.
5. Platform support#
- A harness MAY be platform-specific in its own code (GUI harness drives the desktop; its macOS / Linux backends differ, and Windows may be unimplemented). That's allowed.
- Express "unsupported here" as
AGENTIC_FUNCTIONS = [], via the §1try/exceptor an explicitplatform.system()check — never as an uncaughtNotImplementedErrorat import. Install/registration always succeeds; whether a function is listed reflects whether it can run on this OS. - Detect backends at runtime (
shutil.which,importlib.util.find_spec), don't assume. Document the per-OS setup in the harness README.
6. Discovery & hot-reload (host side — what a harness can rely on)#
- Startup: the host imports every matching folder under
agentics/. - Hot-reload: the host watches
agentics/and, when a new folder appears, runs the same discovery on it and broadcasts aprograms:changedevent so the web UI lists the new harness without a restart. A harness needs to do nothing special — just satisfy §1. - First-party listing: GUI / Research / Wiki are also named in
_programs.pysoopenprogram programs install <name>can clone them by name. Third-party harnesses are installed by cloning intoagentics/directly; discovery treats them identically.
7. Conformance checklist (for a harness author)#
- Repo clones into
agentics/<Repo-Name>/; inside is a package<pkg>/whose folder name equals its import name. -
<pkg>/agentics/__init__.pydefinesAGENTIC_FUNCTIONS = [...]. - That module wraps its imports in
try/except→[]on failure. - Import is cheap: no network / model-download / GUI grab at import.
- LLM access via an injected
runtimeparameter, not self-built. - No required config file / wizard; works at zero-config defaults.
- State under
get_state_dir(), never~/.agenticor another hardcoded home path. -
openprogramis NOT a git dependency inpyproject.toml. - Own third-party deps declared; heavy/native ones behind an extra.
- Platform-unsupported →
AGENTIC_FUNCTIONS = [], not a crash.
Appendix: Implementation Status#
The three first-party harnesses do not yet fully conform to this standard. The remaining gaps:
| Harness | Conformance | Gap to close |
|---|---|---|
| Wiki | closest | agentics/__init__.py, AGENTIC_FUNCTIONS, and the try/except are all present. The default vault path still uses the retired ~/.agentic/memory/wiki and belongs under get_state_dir(). |
| GUI | partial | Exposes functions but has no single <pkg>/agentics/__init__.py with AGENTIC_FUNCTIONS; the decorators are spread across modules, so the entry module still has to be added. Heavy deps are partly behind an extra already. The Windows path must degrade to [] rather than crash. |
| Research | non-conforming | Has no agentics/ sub-package — it uses its own registry.py, which the host's auto-discovery cannot see. It needs research_harness/agentics/__init__.py exposing AGENTIC_FUNCTIONS. |
All three still declare openprogram @ git+… as a dependency, which §4
rules out; removing it is a shared fix.