Agent Governance ToolkitAgent Governance Toolkit 4.1.0 · Python 3.10+
Dashboard
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
47 small wins to finish your pathNext lesson →

Three YAML formats, and which one is real

Three YAML formats for a policy exist in this toolkit, they are mutually incompatible, and the one its own tooling writes is not the one the loader reads, so a file that looks governed can be silently empty.

Last updated: 14 Oct, 2026 · Agent Governance Toolkit 4.1.0

The Policy scope lesson showed one field quietly doing nothing. This lesson is larger and sharper: the loader drops what it does not recognise and fails open, the file the command line generates loads as empty, and the linter and the loader disagree on what a valid rule even is. Each is proven with a run.

The loader drops what it does not recognise

PolicyDocument.from_yaml keeps the fields it knows and silently discards the rest. This file carries a bogus_top key at the top and a severity key on its rule. Both vanish, and with no defaults block the policy fails open.

yaml
version: "1.0"
name: drops
bogus_top: hello
rules:
  - name: allow-refund
    condition: {field: action, operator: eq, value: refund}
    action: allow
    severity: critical
ExampleUnknown keys dropped; no default means allow
import warnings; warnings.simplefilter("ignore")
from agent_os.policies.schema import PolicyDocument

doc = PolicyDocument.from_yaml("drops.yaml")

# the unknown top-level key and the unknown rule key are both gone
print("has bogus_top:", hasattr(doc, "bogus_top"))
print("rule fields  :", list(doc.rules[0].model_dump().keys()))
print("default set? :", doc.defaults.action.value)

Nothing told you the two keys were ignored. A typo in a field name, or a key from a different format, is dropped in the same silence, and a file with no default lets unmatched calls through.

The file the command line writes loads as empty

Running agentmesh init writes a starter policy to policies/default.yaml. It uses a nested policies list, each with its own rules that carry a list of conditions. That shape belongs to a different engine.

yaml
policies:
- enabled: true
  id: default-security
  name: Default Security Policy
  rules:
  - action: block
    conditions:
    - output contains 'password'
    - output contains 'api_key'
    - output contains 'secret'
    id: no-secrets-in-output
    message: Potential secret detected in output
  - action: block
    conditions:
    - peer_trust_score < 500
    id: require-peer-trust
    message: Peer trust score below threshold

Loaded by from_yaml, every one of those keys is unknown at the top level, so all of it is dropped. The document comes back named unnamed, with zero rules, and the fail-open allow default, a policy that blocks nothing.

ExampleThe generated file parses to an empty, fail-open policy
from agent_os.policies.schema import PolicyDocument

# load the exact file agentmesh wrote (shown above)
doc = PolicyDocument.from_yaml("default.yaml")
print("name   :", doc.name)
print("rules  :", len(doc.rules))
print("default:", doc.defaults.action.value)

The linter does not agree it is empty. Run agt lint-policy on the very same file and it reports three hard errors, for the version, name and rules fields it cannot find at the top level.

bash
$ agt lint-policy policies/default.yaml
policies/default.yaml:1: error: Missing required field 'version'
policies/default.yaml:1: error: Missing required field 'name'
policies/default.yaml:1: error: Missing required field 'rules'

3 error(s) found.

The linter and the loader disagree on a rule

The two tools do not even share a schema. This file writes its one condition as a plain string instead of a {field, operator, value} dictionary. The linter accepts it and reports no issues.

yaml
version: "1.0"
name: strcond
rules:
  - name: r1
    condition: "action == refund"
    action: allow
defaults:
  action: deny
bash
$ agt lint-policy strcond.yaml
No issues found.

The loader rejects the identical file. from_yaml validates the condition against its schema, finds a string where a dictionary belongs, and raises before the policy can be used.

ExampleThe loader rejects the rule the linter passed
from agent_os.policies.schema import PolicyDocument

try:
    doc = PolicyDocument.from_yaml("strcond.yaml")
    print("loaded:", doc.name)
except Exception as e:
    # the loader demands a dict condition; the linter was happy with a string
    lines = str(e).splitlines()
    print(type(e).__name__, "at", lines[1].strip())
    print(lines[2].strip().split(" [")[0])

What the three formats mean for you

  • The loader's format is the real one. A top-level version, name, rules and defaults, with each rule's condition a {field, operator, value} dictionary, is what PolicyDocument.from_yaml reads.
  • The generated format is a trap. The nested policies shape that agentmesh init writes loads as an empty, fail-open policy, so a file that looks configured governs nothing.
  • The linter is not the loader. agt lint-policy passing a file, or failing it, tells you nothing reliable about whether from_yaml will read it the way you meant.

Three shapes, one reader

Shapefrom_yaml reads it asagt lint-policy says
Top-level rules, dict conditionsThe real policyPasses a correct file
Nested policies (generated)Empty, fail-openThree missing-field errors
A rule with a string conditionRaises a validation errorNo issues found

Where this bites

  • Trusting a generated starter file, where the agentmesh init output needs rewriting into the loader's format before it governs anything.
  • Trusting a green lint, where a file the linter passes can still fail to load or load as empty.
  • Reviewing a policy, where you load it with from_yaml and print the rule count, rather than reading the YAML and assuming it parsed.
Watch out. The one check that tells the truth is loading the file with from_yaml and asserting it has the rules and the default you expect. A clean lint and a plausible-looking file both pass while the loaded policy governs nothing, which is the worst way for a gate to fail.
Try it yourself
  • Add your own unknown key to drops.yaml, reload, and confirm it is dropped with no warning.
  • Rewrite the agentmesh init file into the loader's format and confirm from_yaml now reports the rules.
  • Fix the string condition into a {field, operator, value} dictionary and watch from_yaml load it.
PreviousPolicy scope

This is what real progress feels like.