AOH packs are engine-neutral operational superpowers. A pack describes what an ops capability is, how an agent should use it, and what each runtime adapter needs to materialize.
Progressive disclosure: only AOH.yaml and at least one skill are required.
Everything else is an opt-in layer for org-scale use.
pack-name/
AOH.yaml # required
skills/<skill-name>/SKILL.md # required (at least one)
teams/<team-name>.yaml # optional
roles/<role-name>.yaml # optional
models/<profile-name>.yaml # optional
runtime-requirements/<requirement-name>.yaml # optional
evals/<eval-name>.yaml # optional
Bindings (kind: Binding) are deliberately NOT part of pack layout — they are
site-specific (role × target) and live in a separate site repository. See Artifact
Kinds below.
-
Pack: top-level metadata and ownership.apiVersion: openagentix.io/v1alpha2. -
Skill: agentskills-compatible instructions plus optional scripts/references/assets. A process skill is a plain skill whose body orchestrates other skills by name (order, branching, escalation). No dedicated kind — it is a documented convention. -
Team: org/project/BU container that groups related operational roles. -
Role: an org/project job function with associated skills, runtime requirements, model profile, and responsibilities. -
ModelProfile: intent-level model routing, such as local worker or frontier unblocker. -
RuntimeRequirement: capabilities the runtime should provide or warn about. -
Eval: scenario prompt and success criteria for one skill, referenced by requiredspec.skill. Evals gate cheap-model trust per skill. -
Binding: site-specific association of a role with a target (e.g.kubeContext+ defaultnamespace). Lives outside packs, in a site repo. Materialized by adapters at install time (--binding); for kubernetes targets every runtime adapter (Hermes, Claude Code, Codex) generates a script the operator runs, chosen byspec.access(defaultscoped; loader rejects any other value):scoped:provision.shcreates a dedicated ServiceAccount bound to an explicit get/list/watch RBAC allowlist (never a*/*wildcard; Secrets are excluded), then writes a scopedkubeconfig(0600) next to the script. This is a hard enforcement boundary — the cluster API server itself rejects mutations from this identity.inherit:prepare-overlay.shwrites NO credentials at all. It resolves the target context's cluster/user entry names from the operator's own merged kubeconfig (via a redactedkubectl config view, never--raw), verifies the result resolves viakubectl config view --minify, and self-checks its own output for credential-shaped content before finishing. It writes a minimalkubeconfig-overlaypinningcurrent-context+ namespace; the agent then runs under the operator's OWN identity, merged in viaKUBECONFIG=<overlay>:<original>. There is NO hard enforcement boundary in this mode — whatever the operator's credentials can do, the agent can do.
Optional
specfields (v0.3, all defaulted, all consumed when aBindingis loaded as part of aSite'sbindingsDir; ignored when a binding is loaded and used standalone):pack(which site pack this binding installs — required if the site defines more than one pack; error if ambiguous),group(single group name, mergesSiteGroup.varsunderspec.targetat a lower precedence than the binding's owntarget),runtime(overridesSite.defaults.runtimeandUserConfig.defaults.runtimefor this one binding). When installed viaaoh install --sitethe rendered ServiceAccount/ClusterRoleBinding names become site-qualified — seeUserConfig/Site/SiteLockbelow. -
UserConfig: the operator's own machine-local defaults,~/.aoh/config.yaml(or$AOH_HOME/config.yaml), loaded lazily — every command that doesn't need it works fine with no config file present at all.apiVersion: openagentix.io/v1alpha2,kind: UserConfig. Fields:packs:(named pack sources,{repo, subdir, ref}structured or a bare local-path string),site:(a default site path/URL, used byaoh listwhen--siteis omitted),registries:(named registry URLs — placeholder for the v0.3 phase C registry work),defaults.runtime(falls back to"claude-code"),defaults.model,defaults.workspaceRoot(tri-state: absent means "the user has not set an opinion," distinct from an explicit value — this matters for the workspace-root consent chain below). -
Site: a fleet's shared, versioned inventory —site.yamlat the root of a separate site repo, alongside abindingsDirof individualBindingfiles (one level deep, sorted, each filename stem required to equal itsmetadata.name, symlinked files/dir rejected, duplicate names rejected).apiVersion: openagentix.io/v1alpha2,kind: Site,metadata.namerequired.specfields:workspaceRoot(ADVISORY ONLY — see the precedence rule below),defaults(runtime/model),targetDefaults(a separate map merged underspec.target, lowest precedence — kept apart fromdefaultsbecause they are different concerns: runtime/model selection vs. target variables),packs(namedPackSources),groups(named, each with avarsmap),bindingsDir.Precedence (three separate chains, not one blended one):
- target vars:
Site.targetDefaults<SiteGroup.vars<Binding.spec.target - runtime: CLI flag >
Binding.spec.runtime>Site.defaults.runtime>UserConfig.defaults.runtime - model:
Site.defaults.model>UserConfig.defaults.model - pack:
Binding.spec.pack> the site's sole pack (if exactly one is defined) > error if the site defines multiple packs and the binding doesn't pick one
Workspace-root consent (tri-state, deliberately not a single fallback chain): effective root =
--workspace-rootCLI flag >UserConfig.defaults.workspaceRoot(only when explicitly set — the tri-state None case falls through) >Site.workspaceRootadvisory (used ONLY when--accept-site-rootis also passed)~/agentsdefault. Whichever source wins, the CLI prints a notice. A site repo (which may be someone else's, pulled over git) must never silently redirect filesystem writes on the operator's own machine. - target vars:
-
SiteLock: the supply-chain pin,site.lock.yamlcommitted next tosite.yaml.apiVersion: openagentix.io/v1alpha2,kind: SiteLock.packs:maps each site pack name to{repo, subdir, requestedRef, resolvedCommit}(git sources) or{local: true, path}(local sources, exempt from commit resolution but still recorded so lock-presence checks are uniform).aoh lockonly writes entries that don't yet exist; it never moves an existingresolvedCommitor changes an existing source.aoh lock --update [<pack>]is the only mover — a source orrequestedRefchange additionally requires--yes(or interactive confirmation). A fan-out install (aoh install --site) fails if the lock is missing, or ifsite.yamlandsite.lock.yamldisagree on a pack's source/subdir/ref — installs always resolve through the LOCK'sresolvedCommit, never by re-resolvingsite.yaml's (possibly movable)refdirectly. Seedocs/installs.mdfor how a resolved commit turns into a materialized, crash-safe workspace.
Adapters generate one invokable command per skill, namespaced under the ops prefix.
The canonical command name is ops:<skill>; each adapter maps the separator to its
runtime's convention:
| Runtime | Surface | Command | Status |
|---|---|---|---|
| Hermes | commands/ops-<skill>.md |
ops-<skill> |
shipped |
| Claude Code | .claude/commands/ops/<skill>.md (subdir → namespace) |
/ops:<skill> |
shipped |
| Codex | .agents/skills/ops-<skill>/SKILL.md (frontmatter name rewritten, no separate command file) |
$ops-<skill> |
shipped |
| OpenCode | command/ops-<skill>.md |
/ops-<skill> |
planned |
The prefix lives in the spec; separator mapping lives in adapters. Codex has no
custom-prompt surface distinct from skills (project-scoped custom prompts are
deprecated on 0.144.x) — the skill itself, wrapper-named ops-<skill>, is both the
capability and the invokable command. See docs/adapters.md for the full workspace
layouts, threat model, and guardrail mapping per runtime.
Materialize any of these surfaces with the runtime-neutral CLI entrypoint:
aoh install --runtime <hermes|claude-code|codex> <pack> --output <dir> [--binding <file>] [--role <name>] [--profile <name>] [--model <hint>]. This is the
single call into ADAPTERS[<runtime>].materialize(...); the older install-hermes*
subcommands remain as unchanged compat handlers, with install-hermes-agent printing
a stderr hint pointing at aoh install --runtime hermes.
For a whole fleet of bindings at once (v0.3), aoh install --site <dir> [--group <g>] [--binding <name>] [--workspace-root <dir>] [--accept-site-root] [--discard-local] fans out across a Site's bindingsDir, one workspace per
binding under <effectiveRoot>/<binding-name>/, resolving pack sources through
site.lock.yaml. aoh list [--site <dir>], aoh config init|get|set, and
aoh lock [--site <dir>] [--update [<pack>]] [--yes] round out the fleet surface —
see docs/reference/cli.md (docs-site) for full flag tables and docs/installs.md
for the crash-safe convergent install model every one of these paths shares.
The other direction — local draft into a shared pack — is
aoh skill promote <name> [--from <dir>] --pack <name> [--pr] (v0.3 phase B):
bare-mirror + per-repo lock + fresh temp worktree cut from a freshly-fetched
default branch + full pack validation in the worktree, then either a
fast-forward-only direct commit (default) or a feature branch + gh pr create
(--pr). See docs/promote.md for the flow, hygiene rules, and conflict model.
AOH models real operational teams:
- Org: company or business unit, such as
acme. - Project: operational scope, such as
platformorml-platform. - Team: a group of roles responsible for a project or business unit, such as
platform-ops. - Role: job function within that scope, such as
sre-platform,devops-automation, ormlops-training. - Skills: capabilities associated with that role, including process skills.
- Runtime requirements: tools/capabilities the runtime should provide.
Runtime adapters decide how to map this into their platform. For Hermes, a role-scoped AOH agent maps to a Hermes profile containing config.yaml, SOUL.md, profile-local skills, and a launch script. A team maps to multiple Hermes profiles, one per role.
aoh validate checks that:
AOH.yamlusesapiVersion: openagentix.io/v1alpha2andkind: Pack.- the pack defines at least one skill; all other artifact kinds are optional.
- every skill has
SKILL.mdfrontmatter with matchingnameand adescription. - role references point to existing skills, model profiles, and runtime requirements.
- team references point to existing roles and model profiles.
- every eval declares
spec.skilland it points to an existing skill. - each YAML artifact has the expected
kindandmetadata.name. - stale v1alpha1 layouts fail loudly: a
workflows/oragents/directory is an error. - bindings load standalone:
apiVersionv1alpha2,kind: Binding,metadata.name,spec.role, and aspec.targetmapping are required; the referenced role is checked against the pack at install time.
apiVersion:openagentix.io/v1alpha1→openagentix.io/v1alpha2in every yaml.kind: Workflowis gone. Delete single-skill wrapper workflows (the skill already covers them). Convert multi-skill workflows into process skills: aSKILL.mdthat lists the constituent skills in order with any branching/escalation logic, added to the owning role'sskills:list.agents/→roles/;kind: AgentRole→kind: Role; the roleworkflows:field is removed.- Every
Evalgains requiredspec.skillnaming the skill it tests. - No compatibility shim and no migrate command — alpha versions carry no compat promise.
AOH declares intent and requirements. Runtime adapters map those declarations into platform-native controls. If a runtime cannot enforce a requirement, the adapter should warn or document the gap rather than claiming enforcement.