CodeDocs Vault

Agents in YAML

Omnigent has no "agent class" you subclass. An agent is a YAML document — identity, system prompt, which model/harness runs the loop, which tools and sub-agents it may call, and what policies gate it. Everything that makes an agent this agent is declarative data, and the runtime reads that data to wire up a runnable loop.

Skills-as-markdown flag: the agent definition itself is YAML, not markdown. (Skills attached to an agent are markdown — SKILL.md with frontmatter — but the agent that loads them is YAML.)

There are two YAML shapes, distinguished by one key:

Shape Discriminator Doc Layout
Single-file launcher name + prompt, no spec_version docs/AGENT_YAML_SPEC.md one .yaml file
Agent image / bundle spec_version: 1 omnigent/spec/AGENTSPEC.md a directory with config.yaml

The single-file shape is what omnigent run agent.yaml is built around and is the focus here. The bundle shape is the portable artifact the server stores as a tarball; the four shipped examples (examples/polly, examples/debby, examples/scribe, examples/kimi_hello.yaml) are mostly bundles using the same field vocabulary.

The discriminator is enforced in code: spec_version is the discriminator key (omnigent/spec/_omnigent_compat.py:140), and a file that declares it is rejected as a single-file YAML and must be a directory bundle (omnigent/spec/_omnigent_compat.py:217, :265-273).


1. The minimal agent

name: hello_agent
prompt: |
  You are a concise assistant. Answer directly and ask a follow-up
  question when the request is ambiguous.
 
executor:
  harness: claude-sdk
  model: databricks-claude-sonnet-4-6
  auth:
    type: databricks
    profile: oss

name + a system prompt + an executor is the whole contract. Detection requires exactly this: .yaml/.yml extension, a top-level mapping, a name key, at least one of prompt/instructions, and no spec_version (omnigent/spec/_omnigent_compat.py:189-222, is_omnigent_yaml).

prompt may be replaced by instructions: AGENTS.md — a path resolved relative to the YAML file, or inline text if it doesn't match a file (docs/AGENT_YAML_SPEC.md:28-29). At least one system-prompt key must be present or the load fails loud (_OMNIGENT_SYSTEM_PROMPT_KEYS, omnigent/spec/_omnigent_compat.py:139).


2. Field reference

Every field below is repo-grounded — each ties to the code that reads it.

Top-level

Field Purpose Consumed at
name Stable id shown in sessions/logs; required for detection. omnigent/spec/_omnigent_compat.py:219
prompt Inline system prompt. _OMNIGENT_SYSTEM_PROMPT_KEYS, omnigent/spec/_omnigent_compat.py:139
instructions Inline text or a path to an instructions file; takes precedence over prompt. docs/AGENT_YAML_SPEC.md:37
description Free-form summary. omnigent/spec/_omnigent_compat.py (carried)
executor Harness + model + auth. omnigent/inner/loader.py:259-261, _parse_executor_spec omnigent/inner/loader.py:617
tools MCP servers, Python function tools, sub-agents, handoffs, inherited tools. docs/AGENT_YAML_SPEC.md:198; tools.agentsomnigent/spec/omnigent.py:358-366
policies / guardrails.policies Guardrails inspecting requests/responses/tool-calls/tool-results. omnigent/spec/omnigent.py:214-224; handlers omnigent/inner/nessie/policies.py
params Typed user parameters readable by tools/skills. docs/AGENT_YAML_SPEC.md:41
os_env Enables local OS tools (sys_os_read/write/edit/shell). _parse_os_env_spec omnigent/inner/loader.py:648; forwarded omnigent/spec/omnigent.py:195
terminals Named interactive shell environments the agent can launch. docs/AGENT_YAML_SPEC.md:320
async Whether async work tools are exposed. Default true. docs/AGENT_YAML_SPEC.md:44
cancellable Whether the session can be cancelled. Default true. docs/AGENT_YAML_SPEC.md:45
timers Whether timer tools are exposed. Default false. docs/AGENT_YAML_SPEC.md:46
spawn Registers sys_session_create so the agent can author + launch child agents. examples/polly/config.yaml:16

executor

executor:
  harness: claude-sdk        # which agent loop runs the turn
  model: databricks-claude-opus-4-7
  auth:
    type: databricks
    profile: oss             # Databricks routing profile

_parse_executor_spec reads the flat harness / model / profile and parses auth into a typed dataclass (omnigent/inner/loader.py:639-644). A bare string executor: <model> is also accepted as just the model (omnigent/inner/loader.py:620-621).

The accepted harness ids are a frozen set (OMNIGENT_HARNESSES, omnigent/spec/_omnigent_compat.py:80-106) plus user-facing aliases (OMNIGENT_HARNESS_ALIASES, :108-126):

antigravity  claude-native  claude-sdk  codex  codex-native  copilot
cursor  cursor-native  goose  hermes  kimi  kiro-native  openai-agents
open-responses  opencode-native  pi  qwen   (+ *-native variants)

An unrecognised harness fails validation with must be one of [...] (validate_omnigent_executor, omnigent/spec/_omnigent_compat.py:179-183). Aliases (claude, kimi-code, opencode, github-copilot, …) are canonicalised before dispatch (canonicalize_harness).

Bundle examples use a nested variant, executor.type: omnigent with the harness under executor.config:

executor:
  type: omnigent
  config:
    harness: claude-sdk

Here executor.config.harness is required and validated against the same set (omnigent/spec/_omnigent_compat.py:172-183). When no model is pinned, the harness resolves the configured provider's default (examples/debby/config.yaml:30-34); when no harness is set, it's inferred from the model prefix (_infer_harness_from_model, omnigent/spec/omnigent.py:164-166).

Three harnesses are special-cased in the spec doc: cursor talks only to Cursor's backend (no Databricks gateway, use CURSOR_API_KEY); antigravity is Gemini-native; copilot needs a GitHub token with Copilot access; kimi authenticates through its own kimi login and declares no executor.auth (docs/AGENT_YAML_SPEC.md:62-141).

os_env — local OS access

os_env:
  type: caller_process
  cwd: .
  sandbox:
    type: linux_bwrap        # omit to get the platform default
    write_paths: [.]
    allow_network: true

Declaring os_env is what registers the filesystem/shell tools (sys_os_read / sys_os_write / sys_os_edit / sys_os_shell — filesystem comes bundled with a shell), parsed by _parse_os_env_spec (omnigent/inner/loader.py:648) and forwarded onto the spec at omnigent/spec/omnigent.py:195. Omit sandbox.type and Omnigent picks linux_bwrap on Linux / darwin_seatbelt on macOS, so the same YAML is cross-platform (docs/AGENT_YAML_SPEC.md:191-195). Trusted local dev uses sandbox.type: none (every example does, to stay loadable on macOS).

Some harnesses own their tools internally and need no os_envkimi advertises handles_tools_internally=True, so adding os_env would just duplicate every op against Omnigent's dispatch path (examples/kimi_hello.yaml:36-40).

tools

Tools are declared by name under tools. Four kinds:

tools:
  github:                    # MCP server (command or url)
    type: mcp
    command: uv
    args: [run, python, -m, my_package.github_mcp]
    tools: [search_issues, get_pull_request]
 
  summarize_file:            # Python function tool
    type: function
    description: Summarize a local text file.
    callable: my_package.tools.summarize_file
    parameters: { type: object, properties: { path: { type: string } }, required: [path] }
 
  reviewer:                  # sub-agent tool — its own harness/model
    type: agent
    prompt: |
      You are a careful code reviewer. Focus on correctness, tests, security.
    executor: { harness: claude-sdk, model: databricks-claude-sonnet-4-6 }
    os_env: inherit
    pass_history: true
    max_sessions: 2
 
  agents:                    # bundle shorthand: sub-agent dir names
    - claude_code
    - codex
    - pi

tools.agents lists sub-agent names; each becomes a callable AgentTool the inner harness surfaces to the LLM (omnigent/spec/omnigent.py:358-366). Listing a sub-agent is sufficient to call it — no separate agent.call builtin (omnigent/spec/AGENTSPEC.md:114-118). The model is allowlist-only: anything not listed is rejected at call time (omnigent/spec/AGENTSPEC.md:345-349). tools.<name>: inherit inherits a parent tool; spec: self clones the parent spec (docs/AGENT_YAML_SPEC.md:292-293).

policies / guardrails

guardrails:
  ask_timeout: 86400         # human-approval window for an ASK
  policies:
    blast_radius:
      type: function
      on: [tool_call]
      function:
        path: omnigent.inner.nessie.policies.blast_radius
        arguments: { gate_pushes: false }

A policy names a dotted-path handler, an on: phase list (request, response, tool_call, tool_result), and arguments/factory_params for a factory. The compiled engine runs before the harness receives a turn (omnigent/spec/omnigent.py:214-224). Shipped handlers (omnigent/inner/nessie/policies.py):

terminals

terminals:
  shell:
    command: bash
    args: [-l]
    os_env: inherit            # same sandbox as the agent
    allow_cwd_override: true
    allow_sandbox_override: false

Named interactive shells the agent launches with sys_terminal_launch — for long-running processes (dev servers, watchers, log tails) that don't fit sys_os_shell's one-shot blocking model (examples/polly/config.yaml:261-269).


3. From YAML to a running loop

omnigent run agent.yaml
        │
        ▼
spec.load(path)                         omnigent/spec/__init__.py:159
        │  source.is_file()  →  is_omnigent_yaml(path)?     :241-250
        ▼  yes
load_omnigent_yaml(path)                omnigent/spec/_omnigent_compat.py:288
        │
        ├─ load_agent_def(path)         omnigent/inner/loader.py:75
        │     parses executor/os_env/tools/policies → AgentDef
        │
        ├─ agent_def_to_agent_spec(...)  omnigent/spec/omnigent.py
        │     translate to typed AgentSpec (executor.type="omnigent")
        │
        └─ validate(spec)               omnigent/spec/validator.py
              validate_omnigent_executor  omnigent/spec/_omnigent_compat.py:146
        │
        ▼
AgentSpec  →  harness factory  →  ExecutorAdapter(executor_factory=…)
                                  e.g. omnigent/inner/claude_sdk_harness.py:309

Step by step:

  1. Dispatch. spec.load branches on the source. A file that passes is_omnigent_yaml routes to the single-file adapter (omnigent/__init__.py:245-250); a directory parses as a bundle; a YAML that fails detection gets a precise diagnosis instead of a misleading tarball error (diagnose_yaml_rejection, omnigent/__init__.py:251-262).

  2. Parse. load_agent_def reads the raw YAML into an AgentDef, pulling executor (omnigent/inner/loader.py:259-261), os_env, tools, and policies into typed sub-objects.

  3. Translate. agent_def_to_agent_spec lowers the AgentDef into the canonical typed AgentSpec with executor.type == "omnigent", inferring the harness from the model when unset (omnigent/spec/omnigent.py:164-166) and turning tools.agents into AgentTool entries (omnigent/spec/omnigent.py:358-366).

  4. Validate. validate runs the shared validator; validate_omnigent_executor enforces the harness allowlist and rejects fields the harness manages itself (compaction — the harness owns its context window, omnigent/spec/_omnigent_compat.py:166-171). Failure raises OmnigentError naming the exact field — fail-loud.

  5. Instantiate. The validated AgentSpec's executor.harness selects a per-harness ExecutorAdapter, each built from an executor_factory (e.g. omnigent/inner/claude_sdk_harness.py:309, omnigent/inner/codex_harness.py:328, omnigent/inner/cursor_harness.py:144, omnigent/inner/copilot_harness.py:144). That adapter is the agent loop: it owns the turn, the tool dispatch, and the model client. The YAML's os_env, policies, and sub-agents are wired around it. The harness id is normalised underscore→hyphen against _UCODE_HARNESS_CONFIGS (omnigent/runtime/workflow.py:182) on the way to the runtime.

So the harness id in YAML is a late-bound dispatch key: the same spec maps onto Claude Code, Codex, Cursor, Gemini, Kimi, Pi, etc. by swapping one string.

The bundle path differs at steps 2-4. A directory bundle (spec_version: 1, the four shipped examples) skips load_omnigent_yaml and parses directly: spec.loadparse(root) (omnigent/spec/parser.py:115, reading config.yaml, sub-objects via _parse_executor :487 / _parse_os_env :643 / _parse_guardrails :2517 / _discover_sub_agents :2475) → validate(spec) (omnigent/spec/validator.py:77). Function-policy handlers stay unresolved dotted strings until build time, when resolve_function_policy (omnigent/policies/function.py:256) imports the callable. From the validated AgentSpec onward (step 5) both paths converge.


4. Two worked examples

examples/scribe — a docs orchestrator (bundle)

A directory bundle: config.yaml plus agents/researcher/, agents/reviewer/, and skills/{changelog,migration-guide,api-docs}/.

The cross-vendor structure is the whole point: review is done by a model from a different vendor than the author.

examples/polly — a coding orchestrator (bundle)

Polly is the multi-agent coding pipeline — the closest thing in the repo to a Swisscheese-style writer→reviewer→fixer loop.

examples/kimi_hello.yaml is the opposite extreme — a single-file launcher with just name + executor.harness: kimi + prompt, the exact shape omnigent run --harness kimi generates internally (examples/kimi_hello.yaml:9-16). examples/debby sits between: a bundle that fans every question out to a claude and a gpt sub-agent and shows both, with an optional opencode third voice.


5. How YAML composes with native harnesses and policies

The YAML never implements an agent loop — it selects one and wraps it:

The net: harness = how the loop runs, policy = what it may do, skill = how it should think, sub-agents = who else it can call — all four declared as data in one YAML tree.


6. Annotated reference YAML

# ── identity ────────────────────────────────────────────────
name: coding_agent                 # stable id in sessions/logs (required)
description: Plans and delegates coding work.   # free-form summary
 
# ── system prompt ───────────────────────────────────────────
prompt: |                          # inline prompt; or `instructions: AGENTS.md`
  You are a coding agent. Inspect files before editing, run targeted
  tests, and summarize changes with validation results.
 
# ── which loop runs the turn ────────────────────────────────
executor:
  harness: claude-sdk              # dispatch key → an ExecutorAdapter
  model: databricks-claude-sonnet-4-6   # omit to use provider default
  auth:
    type: databricks               # provider auth; cursor/kimi use their own
    profile: oss
 
# ── interaction toggles ─────────────────────────────────────
async: true                        # expose async work tools (default true)
cancellable: true                  # session can be cancelled (default true)
timers: false                      # timer tools off (default false)
spawn: false                       # set true to allow self-authored children
 
# ── local OS access (registers sys_os_* tools) ──────────────
os_env:
  type: caller_process
  cwd: .
  sandbox:
    type: linux_bwrap              # omit → platform default
    write_paths: [.]
    allow_network: true
 
# ── interactive shells the agent can launch ─────────────────
terminals:
  zsh:
    command: zsh
    args: [-l]
    os_env: inherit                # same sandbox as the agent
    allow_cwd_override: true
 
# ── guardrails: evaluated before the harness gets the turn ──
guardrails:
  ask_timeout: 86400               # day-long human-approval window
  policies:
    blast_radius:
      type: function
      on: [tool_call]              # phase: request|response|tool_call|tool_result
      function:
        path: omnigent.inner.nessie.policies.blast_radius   # dotted handler
        arguments: { gate_pushes: false }   # catastrophic DENY only
 
# ── tools: functions, MCP servers, and sub-agents ───────────
tools:
  repo_search:                     # Python function tool
    type: function
    description: Search repository files for a pattern.
    callable: my_package.tools.repo_search
    parameters:
      type: object
      properties: { query: { type: string } }
      required: [query]
  agents:                          # sub-agents; listing one = allowed to call it
    - reviewer                     # resolves to agents/reviewer/ (bundle)

For Swisscheese

Polly is a writer→reviewer→fixer pipeline expressed entirely in YAML — worth lifting wholesale:

For the AI Act platform


Pitfalls