Understand your YAML files
Read every part of an agent definition, connect agents in an application, and give governed tools explicit rules.
Which file do you edit?#
agent.yaml describes one agent. application.yaml lists the agents deployed together and their messaging permissions. rules.yaml supplies conditions for governed tools. Keep these files at their documented locations:
customer-care/
application.yaml
refund-helper/
agent.yaml
rules.yaml
tools.py
skills/order-status/SKILL.md
skills/refunds/SKILL.md
support-assistant/
agent.yamlDownload the complete sample application
The archive includes the sample Python tools and skills, plus all four YAML files shown below. It uses synthetic orders; it does not connect to a business system. Its model name is taken from the reviewed starter—select a model supported by your environment. The dollar limits are illustrative.
You can upload the archive from Build → New agent → Upload a folder or zip, or unpack and check it with the supported CLI. Use a fresh practice environment or rename the application and both agents consistently if these names already exist. This governed example needs the decision engine before its full check or deployment can succeed.
Read the YAML notation#
- Indentation defines structure. Use spaces, keep sibling keys aligned, and indent nested keys two spaces.
- A dash starts a list item.
skills: [order-status, refunds]is the compact form of the two-line list below. |preserves line breaks. Use it for instructions you want to write as paragraphs.>-folds lines into one string. The rules example uses it to wrap a long condition without changing its meaning.- Use real booleans and numbers. Write
true,false, and0.25. Quote text that includes a colon followed by a space, or other YAML punctuation. - Write each key once. Unknown fields are rejected by the typed schema. Add a budget field inside the existing
budget:block rather than pasting a second block.
A complete agent.yaml#
This example starts with the refund-helper sample, enables a read-only order lookup, and governs refunds with the rules below. Its accept_from entry is paired with the application’s messaging declaration.
# customer-care/refund-helper/agent.yaml
name: refund-helper
description: Answers order questions and handles sample refunds.
# Replace with a model your environment supports (a known name or provider/model).
model: your-model-id
instructions: |
You are the customer helper for Copperleaf Mugs.
Ask for the order number when it is missing.
Look up the order before answering questions about it.
Never promise anything a tool has not confirmed.
greeting: Hi! What is your order number?
starters:
- Where is order A1001?
- My mug from order A1002 arrived cracked.
skills:
- order-status
- refunds
tools:
- name: get_order
reads: true
- name: issue_refund
governed: true
approval: manager
rules: rules.yaml
runtime:
state: conversation
# Illustrative limits, not product defaults or prices.
budget:
per_turn_usd: 0.25
per_conversation_usd: 1.00
per_day_usd: 5.00
per_month_usd: 25.00
max_output_tokens: 2000
# The matching edge also appears in application.yaml.
accept_from:
- support-assistant| Field | Meaning and validation |
|---|---|
name | Required. 2–63 lowercase letters, digits, or hyphens, starting with a letter. In an application it must match the listed agent folder. |
instructions | Required, non-empty text. Describes the job and expected behavior. It is guidance to the model, not a substitute for an enforced tool rule. |
description | Optional, up to 400 characters. A short description for people inspecting the agent. |
model | Optional in the schema; defaults to the configured source default. Specify a model intentionally. Known bare prefixes resolve a provider; otherwise use provider/model. Model credentials are configured outside this file. |
greeting | Optional opening text shown in chat. It is not a model turn or a tool result. |
starters | Optional list of up to six prompts shown as choices in chat. Selecting one sends that prompt; do not include customer secrets. |
skills | Optional list of skill folder names. Every listed skill must have a matching skills/<name>/SKILL.md with a matching front-matter name and a non-empty description. |
tools | Optional list of tool names or tool objects. Each Python function must exist in tools.py and have its declared tool contract. Agent messaging adds the built-in ask_agent tool when peers are declared. |
rules | Optional file path, default rules.yaml. A governed tool requires its rules file. A rules file with no governed tools is reported as unused. |
Tool switches change execution#
A short entry such as tools: [get_order, issue_refund] only names tools. It does not mark a lookup read-only or attach policy. Use the object form to set those properties:
| Field | Meaning and validation |
|---|---|
name | The declared tool name. |
reads | Default false. Set true only for an operation that has no effects. It can then supply rule context and run in a browser draft test. This is an owner declaration, not an automatic inspection of Python code. |
governed | Default false. Set true to require a decision-engine verdict before the tool runs. |
approval | Optional role name, 1–64 characters. On an ungoverned tool, every call waits for this role. On a governed tool, calls that receive ask wait for it. |
reads: true cannot be combined with governed or approval on the same tool. A read-only context tool cannot also wait for an effect approval.
Browser starter step: the reviewed refund-helper starter lists plain tool names. Select reads only beside get_order and Save switches before expecting its lookup to execute in Talk to the draft. Leave issue_refund unmarked as a read.
Setting governed: true without supplying rules causes the check to fail. Use the complete downloadable example or the CLI round trip to add the rules file; the browser switches do not author that policy for you.
Budget and working state#
| Field | Meaning and validation |
|---|---|
per_turn_usd | Optional positive dollar limit for a turn, which may involve multiple model calls. |
per_conversation_usd | Optional positive limit across the conversation. |
per_day_usd | Optional positive daily limit. |
per_month_usd | Optional positive monthly limit. |
max_output_tokens | Optional integer from 256 to 1,000,000. Caps each model call’s output and is used in its worst-case cost estimate. The provider may impose a lower limit. Omitted: the model’s own output cap is used. |
The declared dollar caps must not decrease as their scope grows: a turn cap above the conversation cap is rejected. A cap too small for even one call’s output is rejected when pricing is available. Omitting a dollar field sets no agent-level limit for that scope; tenant and authoring limits can still apply. Do not use 0 to mean unlimited. See Usage and budgets.
runtime.state accepts none (default), conversation, or agent. Tools use helixor_agent.state_dir() for up to 256 KiB of working files. Conversation scope separates each conversation; agent scope shares state across that agent’s conversations in the tenant.
Reads and draft tests do not save state. Successful effect calls can save it with revision checks. AGENT_STATE_NOT_KEPT means the call ran but its state could not be stored; investigate the effect rather than repeating it.
application.yaml: membership and messaging#
# customer-care/application.yaml
name: customer-care
description: Order help and sample refunds.
agents:
- refund-helper
- support-assistant
messaging:
- from: support-assistant
to: refund-helper
why: Order status and refund questions belong to the refund helper.| Field | Meaning and validation |
|---|---|
name | Required. The same 2–63-character name pattern as an agent. |
description | Optional description, up to 400 characters. |
agents | Required non-empty list. Each entry names an immediate agent subfolder; the folder’s agent name must agree. |
messaging | Optional directed edges. Each entry has from, to, and an optional why up to 300 characters. An edge permits one direction, not both. |
invoke | Optional application-level trigger configuration: zone, max_concurrent, daily/monthly budget, and defaults. Trigger availability is a separate runtime check; see below. |
The application edge is only one part of permission. The sender must declare the recipient in agents:, and the recipient must name the sender in accept_from:. The complete sender file for this example is:
# customer-care/support-assistant/agent.yaml
name: support-assistant
description: Routes order questions to the refund helper.
model: your-model-id
instructions: |
Help the customer describe their order question.
Ask for the order number when it is missing.
Use ask_agent to send order and refund questions to refund-helper.
Relay its result without inventing order facts or promising an action.
agents:
- name: refund-helper
why: Order status and refund questions belong to the refund helper.
budget:
max_output_tokens: 2000Download the sender’s agent.yaml. The refund helper’s accept_from: [support-assistant] completes the agreement. Check the whole application to validate all three declarations.
For members of the same application, use the peer’s name and why; do not add local path or remote url. Peer declarations outside this arrangement can use a local path for development, or url plus key_env for a remote service. Keep the key value in the named environment variable, not in YAML.
Use application.yaml in new work. Older group.yaml is read with a rename warning; having both files produces APPLICATION_FILE_CONFLICT.
rules.yaml: facts, conditions, and verdicts#
# customer-care/refund-helper/rules.yaml
context:
order:
tool: get_order
args:
order_id: call.args.order_id
tools:
- issue_refund
rules:
- id: order-refund-cap
when: >-
call.tool == "issue_refund" and
call.args.amount + order.refunded_total > 500
verdict: deny
why: Refunds totalling over $500 on an order need the returns team.
- id: manager-over-100
when: >-
call.tool == "issue_refund" and
call.args.amount + order.refunded_total > 100
verdict: ask
why: Refunds totalling over $100 on an order need a manager.
default: allow
default_why: Refunds up to $100 on an order may go ahead.- Fetch context. Before an
issue_refundcall, read the order usingget_order. The mapping takesorder_idfrom the pending call’s arguments. Context arguments usecall.args.<argument>, not arbitrary Python expressions. - Evaluate conditions.
call.toolidentifies the requested tool;call.args.amountis the requested refund;order.refunded_totalis the fetched prior total. - Choose the most restrictive match.
denytakes precedence overask, which takes precedence overallow. This is not a first-match list. - Use the explicit default when nothing matches.
defaultis required. This sample’sallowis appropriate only to its narrow refund scenario; review it before adding other governed tools.
| Field | Meaning and validation |
|---|---|
context | Optional named facts, at most 20. Each fact has a read-only tool, argument mappings, and optional tools limiting which governed calls fetch it. Without that list it is fetched for every governed tool. |
rules | Up to 200 entries. Each needs a unique id, non-empty when, verdict and non-empty why (up to 300 characters). The ID default is reserved. |
when | A condition over call and declared context facts. Function calls, lambdas and private attributes are rejected. Local syntax checking does not replace decision-engine admission. |
default / default_why | The required fallback verdict for no matching condition and its explanation. It is not permission to continue after a fact fetch or evaluation error. |
With no previous refund, $100 is allowed, $140 asks, and $640 is denied in this sample. With $80 already refunded, a new $40 asks because the cumulative total is $120. The > comparisons mean exactly $100 does not trigger the ask rule, and exactly $500 still requires approval.
Missing context stops the call with GATE_CONTEXT_UNAVAILABLE. A required field that is missing or null prevents evaluation with POLICY_RULE_EVALUATION_FAILED. Neither condition falls through to default: allow. Use the Rules tester before relying on a policy.
Optional fields and availability#
outcomes: can name up to 20 distinct outcomes. Each entry has a name and exactly one declared tool or installed work_list. For example:
outcomes:
- name: refund-issued
tool: issue_refundThis declares what to count; it does not authorize the tool or certify the quality of its result.
Without invoke:, the agent retains its implicit manual chat/messages trigger. If you add an explicit manual configuration, keep the chat entry when you want chat to remain available:
invoke:
manual:
- id: chat
says: A customer chats with the agent.
via: [chat, api]
- id: order-review
says: A manager requests an order review.
via: [console, api]
roles: [manager]
inputs:
order_id:
type: string
pattern: '^A[0-9]{4}$'
max_concurrent: 2Use the six worked trigger examples for configuration, prompt files, browser usage, and runtime availability. Scheduled, event, and workflow-step declarations can validate while reporting not_firing in the reviewed release. plan.enabled is refused until its store integration is available. Do not copy future runtime.session, secrets, or connector examples into the current schema; its implemented runtime field is state.
From a browser control to a YAML field#
| Field | Meaning and validation |
|---|---|
Instructions | Writes instructions, greeting, and starters in agent.yaml. |
Skills | Maintains the selected skills and their SKILL.md files; the description tells the model when to use each skill. |
Tools → Save switches | Writes tool objects under tools:. Python implementations remain in tools.py and are read-only online. |
Budget → Save budget | Writes the corresponding fields under budget:. Empty fields omit an agent cap; they do not remove tenant limits. |
Invoke | Read-only in the reviewed browser editor; edit invoke: in the downloaded/pulled agent folder. |
Download / pull / push | Transfers the bundle. YAML edits remain a draft until deployment. Keep pull metadata so stale changes can be detected. |
See the illustrated browser walkthrough for the actual controls and the CLI round trip for code and configuration changes outside the online editor.
Validate changes in order#
# From the folder containing customer-care:
helixor agent check customer-care
helixor agent dev customer-care --port 8840
# For a folder pulled from an online draft:
helixor agent push --draft customer-care- Fix YAML structure, unknown keys, and the reported file/line errors.
- Check that skill files and tool functions exist, and that application messaging agrees in all three places.
- For governed tools, connect the configured decision engine and verify its rule admission. A syntactically valid file is not an admitted policy.
- Test reads in the browser draft. Test allow/ask/deny in Rules using practice data.
- Review the application deployment before making the new version live. A file save does not update the deployed agent.
Typical errors include reads: true combined with approval, a governed tool with no rules file, a skill without front matter, and a budget below one call’s estimate. Use the diagnostic to fix the blocked step.
Shared case boards (development preview)#
blackboard:
read: true
publish: falseBoth flags default to false. Publishing shares only a runtime-captured successful read result. Requires the matching preview SDK and agent runtime, and Python 3.12+. See the two-agent example and full reference. Board observations are not deleted by the service: when a case expires it is sealed, and removing old boards is the deployment's retention job.
Web search and browser (governed, off by default)#
network:
mode: restricted # off | restricted; open is refused
allow:
- origin: https://example.com
addresses: [104.20.23.154, 172.66.147.243]
search:
provider: fixture # brave (key_env: BRAVE_SEARCH_API_KEY) | duckduckgo_html | fixture
fixture: search-fixture.json
browser:
plans:
- id: read-example-com
description: Read the page at https://example.com/.
steps:
- {kind: navigate, url: "https://example.com/"}
success_url: https://example.com/
success_target: {role: AXHeading, label: Example Domain}
extract:
- {name: heading, selector: h1, kind: text}An agent has web_search or browser_run only when it declares search: or browser:. Both reach only the origins under network.allow. Every call is decided by rules.yaml and recorded before it runs. The browser runs each plan once in a sandboxed SmolVM guest on a browser pool, never in the service; its egress is enforced at the VM boundary on a Linux pool host, and a macOS host refuses (BROWSER_EGRESS_UNENFORCEABLE). A Brave key comes from the service environment named by key_env, never from the file. Logins and credentials are not supported yet. Reference: helixor-cli/docs/agent-web-tools.md.