openheron
Chapter 4

Writing configuration

4.1 Agents (agents/<name>.md)

An agent is a markdown file: frontmatter + body. The body is the prompt the model sees.

---
description: "task: the concrete executor. Writes files per the task order."
model: deepseek/deepseek-flash
max_iterations: 50
reasoning: low
disabled_tools: [kb_index]
summarize_at: 80%
---

You are the executing agent. Once you receive a task:
1. Read the relevant files first
2. Work through the task order item by item
3. Run the tests afterwards and paste the failures verbatim

Available frontmatter keys:

Key Meaning
description What this agent is for (the parent agent uses it to decide whom to delegate to)
model <vendor>/<model>, required, never inherited and never falls back
max_iterations Cap on tool-loop iterations
summarize_at When this agent’s context is compacted into a summary: 80% of the model’s window (the default), an absolute token count (104858), or 0 for never
subagents Whom it may delegate to (see §5)
disabled_tools Tool deny-list effective for this agent
skills The skills this agent may use — an allow-list of the folders under skills/ (not declared means every skill, an empty list means none)
temperature / max_tokens Sampling parameters
reasoning Reasoning tier: off / low / high / max (names defined by the vendor profile)

★ One wrong frontmatter key is a startup error and is never ignored — on purpose: a configuration that fails silently is far harder to debug than one that fails loudly.

★ model is not inherited. The model the parent agent uses does not carry over to sub-agents — every agent has to name its own. That is exactly where “expert gets expert model” lands.

★ A reasoning tier only works if the model profile declares it. An undeclared tier is silently ignored: an agent says reasoning: high, the endpoint has no high, and the effect is “no thinking at all” — with no error and no message. See §4.2.

★ summarize_at is what keeps a long conversation alive. Past that point the conversation is replaced by a summary, and the summary is what the agent carries forward from there on. It comes in three spellings:

Spelling Means
summarize_at: 80% 80% of the context_window of the model this agent runs on
summarize_at: 104858 An absolute token count
summarize_at: 0 Never compact — the context grows until the endpoint refuses the request

Leave the key out and the agent gets 80% of its window — compaction is on by default. 80–90% is the usual range: a request that exactly fills the window is a request that gets refused, and the room that is left is what the summary itself needs.

★ A percentage needs a window. If the model declares no context_window, there is nothing to take 80% of: that agent never compacts, and openheron says so when it loads the configuration. Declare context_window in llm/<vendor>.yaml, or set an absolute summarize_at on the agent.

4.2 Model profiles (llm/<vendor>.yaml)

type: openai_compat
base_url: https://api.deepseek.com
api_key: ${DEEPSEEK_API_KEY}
timeout: 5m

models:
  - id: deepseek-flash
    context_window: 131072
    reasoning:
      off:  { reasoning_effort: none, thinking: { type: disabled } }
      low:  { reasoning_effort: low,  thinking: { type: enabled } }
      high: { reasoning_effort: high, thinking: { type: enabled } }
      max:  { reasoning_effort: max,  thinking: { type: enabled } }
    reasoning_default: off
Field Meaning
type Protocol type; currently openai_compat
base_url The endpoint, including the path, copied from the vendor docs
api_key An environment variable name, written as ${NAME} — never a literal
timeout Timeout, with a unit (5m / 30m)
models[].id Model name
models[].context_window Context window — the size the compaction threshold is measured against
models[].reasoning Tier table: tier name → what this endpoint actually sends
models[].reasoning_default Which tier to use when the agent names no reasoning
models[].timeout Overrides the vendor-level timeout (thinking models usually need more)
hint_keys The endpoint’s field names, used to give an accurate hint when a request is rejected
extra Vendor-level dialect, passed through to every request

★ api_key may only be an environment variable name (${OPENAI_API_KEY}) or a file reference (@file:/path). Writing a literal secret into the configuration makes the config editor refuse to save it — secrets do not belong in the repository.

★ The compaction threshold belongs to the agent, not to the model. context_window is the model’s; summarize_at is the agent’s, because how much of that window this agent fills before its history is summarized is this agent’s own policy. One model can serve several agents with different policies. A summarize_at left in a model profile is ignored, and openheron says so when it loads the configuration.

Compaction shortens the context, not the record. The messages that were compacted away stay in the transcript and stay readable; the summary is written next to them. Your own words survive the cut: the summary keeps them.

★ The reasoning tier table is not optional. It is the translation layer between “the agent writes reasoning: high” and “how this endpoint expresses high”. A tier missing from the table is silently ignored, so “I turned on high and it still does not think” shows up in the most hidden way possible.

4.3 The approval policy (policy/permissions.yaml)

ask:
  - write_file
  - edit_file
  - execute
  - kb_index
deny: []

There are only two columns, and the semantics are plain:

  • Listed under ask: every call needs your nod.
  • Listed under deny: nobody may use it.
  • Listed in neither: defaults to allow (straight through).

★ deny is global. The disabled_tools in agents/*.md only affects that one agent and cannot relax the outer deny. It is the one guarantee a sub-agent cannot loosen.

4.4 External tools (tools/)

Enabled by the presence of the file — no code change needed. Four file names are recognised today:

File Provides
http.yaml A generic HTTP request tool
web_search.yaml Web search
wikipedia.yaml Encyclopedia lookup
kb.yaml The knowledge base (see §7)

★ Only these four file names are accepted under tools/; any other name is a startup error and is never silently ignored.

★ openheron init writes nothing under tools/ by default — that whole directory is yours to write. Pass --tools and it writes the four files above. Not writing a file means not enabled: the tool does not appear in the agent’s tool table, and the model cannot see it.

4.5 Process parameters (server/config.yaml)

Field Default Meaning
listen_addr :8080 Listen address
data_dir state Where sessions and checkpoints live
static_dir web/dist Frontend build output (used when frontend_mode: dir)
frontend_mode embed embed uses the interface baked into the binary; dir reads from disk
start_timeout 60s Startup timeout
lease_ttl 10m How long a run’s lease survives without progress — longer than the provider’s own timeout
agent.max_depth 1 Cap on delegation depth

★ server/ is not hot-reloaded — a change only takes effect after a restart. That is why it is read-only in the config editor, and submitting it fails with section "server" is not editable.

These environment variables override the file and take precedence: LISTEN_ADDR, DATA_DIR, PROJECT_ROOT, FRONTEND_MODE, STATIC_DIR.

4.6 Skills (skills/)

skills/<name>/SKILL.md, following eino’s native skill convention.

★ Skills are looked up by a different rule: $EINO_EXT_SKILLS_DIR first, and only if that is unset does it search upwards from the current directory for .openheron/skills. That differs from the config root’s “never search upwards” — skills are a directory tree, and searching upwards lets you use skills from your home directory.

★ Which skills an agent may use is declared by that agent. The skills key in agents/<name>.md is an allow-list, and the names are the folders under skills/:

skills: [refactor, review]

Not declaring the key means every skill in the directory; declaring an empty list means none. A skill outside the list is not advertised to the model, and asking for it by name fails — a model cannot load what it cannot see. A name that is not in the directory is reported in the log when the agent is built, rather than ignored.