Skip to content

Charter DSL v0 — Canonical Specification

Version: 0.1 | Status: Accepted | Date: 2026-05-27

This document is the authoritative Charter DSL v0 reference. It supersedes prior exploration drafts as canonical authoring guidance. Implementers write charters in this syntax; the compiler produces typed runtime targets; CLI and IDE tooling consume the same schema.

Related: standards/dsl-style.md, standards/trace-naming.md, standards/adr-style.md, ADR-0047 (Agent Charter), ADR-0051 (Tool Registry Allowlists), ADR-0052 (Policy Resolution)


1. Design Principles

  1. Exact identity, no wildcards. Tool names, registry values, and grant targets must be exact strings. Wildcards are invalid in v0.
  2. Default deny, deterministic resolution. permissions.default is always deny. Most-specific scope wins; ties deny. Declaration order does not change authorization.
  3. Compiler-time activation. A charter is not active until all gate references and hook references are resolved, the normalized document is hashed, and the hash matches metadata.ratification.hash.
  4. Evidence and trace contracts are first-class. Every constraint declares the evidence types it requires. The trace block declares the spans the runtime must emit.
  5. No embedded logic. No boolean formula language, no placeholder substitution, no executable tokens. Conditions are represented by typed gate or hook bindings only.

2. Canonical Top-Level Shape

charter_dsl: "0.1"
kind: agent_charter | session_charter

metadata:
  charter_id: ...
  version: ...
  status: ...
  policy_release: ...
  authored_by: ...
  implemented_by: ...
  ratification:
    hash: sha256:<hex>     # required when status: accepted
    signed_at: ...

agents: []                 # >=1 for agent_charter, >=2 for session_charter

registry:
  snapshot: ratification_time
  entries: []

constraints: []

sod:
  active: true
  rules: []

permissions:
  default: deny
  resolution:
    specificity_order: [resource, role, tenant, global]
    tie: deny
  allow: []
  deny: []

break_glass: null          # optional — omit when no elevated mode is needed

trace:
  stamp: []
  require_spans: []
  require_evidence: []

3. Grammar Reference (EBNF Sketch)

document          ::= version kind metadata agents registry constraints sod permissions break_glass? trace

version           ::= "charter_dsl" ":" version_string
kind              ::= "kind" ":" ("agent_charter" | "session_charter")

metadata          ::= "metadata" ":" metadata_body
metadata_body     ::= charter_id version_field status policy_release authored_by implemented_by ratification
status            ::= "draft" | "proposed" | "accepted" | "superseded"

agents            ::= "agents" ":" agent+
agent             ::= agent_id actor_id_source role allowed_models allowed_tools allowed_operations?
actor_id_source   ::= "branch_id" | "session_actor_id"

registry          ::= "registry" ":" snapshot entries
snapshot          ::= "snapshot" ":" "ratification_time"
entries           ::= "entries" ":" registry_entry*
registry_entry    ::= category value scope scope_id reason evidence_refs
category          ::= "tool" | "model" | "mcp_endpoint" | "url" | "path_prefix"

constraints       ::= "constraints" ":" constraint+
constraint        ::= constraint_id description binding manager_surface enforcement attach evidence
binding           ::= gate_binding | hook_binding
gate_binding      ::= "gate_id" ":" identifier
hook_binding      ::= "hook_name" ":" identifier "hook_phase" ":" identifier
manager_surface   ::= "ActionManager" | "MessageManager" | "iModelManager" | "DataLogger"
enforcement       ::= "hard" | "soft" | "advisory"
attach            ::= class_attach | action_attach
class_attach      ::= "attach" ":" "level" ":" "class" "tool_class" ":" identifier
action_attach     ::= "attach" ":" "level" ":" "action" "action" ":" identifier tools?

sod               ::= "sod" ":" active sod_rules
sod_rules         ::= "rules" ":" sod_rule*
sod_rule          ::= rule_id conflict_type roles scope because
conflict_type     ::= "transaction_dual_control" | "record_custody" | "audit_independence"
                    | "approval_chain" | "access_control"
sod_scope         ::= "session" | "task" | "global"

permissions       ::= "permissions" ":" default resolution allow deny
default           ::= "default" ":" "deny"
resolution        ::= specificity_order tie
specificity_order ::= "[" "resource" "," "role" "," "tenant" "," "global" "]"
tie               ::= "tie" ":" "deny"
allow             ::= "allow" ":" permission_rule*
deny              ::= "deny" ":" permission_rule*
permission_rule   ::= rule_id scope roles action (tools | resources) requires_evidence? because

break_glass       ::= "break_glass" ":" break_glass_body
break_glass_body  ::= enabled expires_after attestation temporary_grants notifications evidence

trace             ::= "trace" ":" stamp require_spans require_evidence

Expression language is intentionally minimal in v0:

  • No arbitrary boolean expressions.
  • Conditions use typed gate or hook bindings.
  • Matching is exact role, tool, resource, scope, and registry entry comparison.
  • Path matching is prefix-only via category: path_prefix registry entries.
  • Policy resolution is structural: most-specific scope wins, ties deny.

4. Validation Rules

Required Fields

Document top-level: charter_dsl, kind, metadata, agents, registry, constraints, sod, permissions, trace.

Metadata: charter_id, version, status, policy_release, authored_by, implemented_by, ratification.

Agent: agent_id, actor_id_source, role, allowed_models, allowed_tools.

Registry entry: category, value, scope, scope_id, reason, evidence_refs.

Constraint: constraint_id, description, exactly one of gate_id/hook_name, manager_surface, enforcement, attach, evidence.required.

Permissions: default, resolution.specificity_order, resolution.tie, allow, deny.

Trace: stamp, require_spans, require_evidence.

Type And Invariant Checks

  • charter_dsl must be "0.1".
  • metadata.version is a semver string.
  • metadata.status is one of draft, proposed, accepted, superseded.
  • metadata.ratification.hash is required unless status is draft or proposed.
  • agents length is exactly 1 for agent_charter, at least 2 for session_charter.
  • registry.entries[*].value has no wildcards.
  • path_prefix values start and end with /.
  • break_glass.expires_after is at most 30m in v0.
  • Hash fields use sha256:<64-hex-chars> after real compilation.
  • sod.rules[*].roles has exactly two distinct roles declared in agents.

Semantic Checks

  • Reject unknown top-level keys.
  • Reject duplicate charter_id, agent_id, constraint_id, rule_id within one document.
  • Reject tabs.
  • Reject executable tokens in scalar strings: __import__, eval(, exec(, lambda, subprocess.
  • Reject wildcards in tool names, registry values, and temporary grants.
  • Reject constraints with both gate_id and hook_name present.
  • Reject constraints with neither binding.
  • Reject hook_name without hook_phase.
  • Reject attach.level: class without tool_class.
  • Reject attach.level: action without action.
  • Reject accepted charters whose normalized hash does not match the ratification hash.
  • Every agents[*].allowed_tools entry must appear in registry entries.
  • permissions.default must be deny.
  • permissions.resolution.specificity_order must be exactly [resource, role, tenant, global].
  • permissions.resolution.tie must be deny.
  • If sod.active is false, accepted charters are invalid unless a policy release explicitly allows disabled SoD.
  • Break-glass attestation.approver_role must not equal the requesting role.
  • Every required span in trace.require_spans must appear in the canonical registry in standards/trace-naming.md.

5. Charter Examples

Example A: Simple Read-Only Agent Charter

One reader agent with a single allowed tool, exact registry controls, and path-prefix bounding.

charter_dsl: "0.1"
kind: agent_charter

metadata:
  charter_id: charter.simple_reader
  version: "1.0.0"
  status: accepted
  policy_release: policy.gov.v1
  authored_by: human:governance
  implemented_by: agent:implementer
  ratification:
    hash: sha256:1000abcd00000000000000000000000000000000000000000000000000000001
    signed_at: "2026-05-27T00:00:00Z"

agents:
  - agent_id: agent.simple_reader
    actor_id_source: branch_id
    role: reader
    allowed_models: [provider:model-name]
    allowed_tools: [tool.read_file]

registry:
  snapshot: ratification_time
  entries:
    - category: tool
      value: tool.read_file
      scope: agent
      scope_id: agent.simple_reader
      reason: "The reader can inspect approved workspace files."
      evidence_refs: [ev.registry.simple_reader.read_file]
    - category: path_prefix
      value: /workspace/
      scope: agent
      scope_id: agent.simple_reader
      reason: "File access is bounded to the workspace."
      evidence_refs: [ev.registry.simple_reader.workspace]

constraints:
  - constraint_id: gate.registry.read_file
    description: "Read-file calls must match the ratified registry snapshot."
    gate_id: verify_in_registry
    manager_surface: ActionManager
    enforcement: hard
    attach:
      level: action
      action: tool_call
      tools: [tool.read_file]
    evidence:
      required: [GateResult, ToolCallEvidence]
  - constraint_id: gate.path.workspace
    description: "Read-file paths must stay under the workspace prefix."
    gate_id: enforce_path_prefix
    manager_surface: ActionManager
    enforcement: hard
    attach:
      level: action
      action: tool_call
      tools: [tool.read_file]
    evidence:
      required: [GateResult]

sod:
  active: true
  rules: []

permissions:
  default: deny
  resolution:
    specificity_order: [resource, role, tenant, global]
    tie: deny
  allow:
    - rule_id: allow.reader.read_workspace
      scope: role
      roles: [reader]
      action: tool_call
      tools: [tool.read_file]
      resources: [/workspace/]
      requires_evidence: [GateResult, ToolCallEvidence]
      because: "Reader role requires file inspection only."
  deny:
    - rule_id: deny.reader.write_or_shell
      scope: role
      roles: [reader]
      action: tool_call
      tools: [tool.write_file, tool.exec_command]
      because: "Reader role cannot mutate files or run shell commands."

trace:
  stamp: [charter_id, policy_release, agent_id, role]
  require_spans:
    - governance.operation
    - registry.lookup
    - gate.evaluate
    - evidence.emit
  require_evidence:
    - GateResult
    - ToolCallEvidence

Compile targets: one AgentCharter, two registry entries, two action-level hard gates, one empty active SoD policy, one deny-default permission policy.


Example B: Multi-Agent Session Charter With SoD

Four roles: orchestrator (coordinates only), researcher, implementer, and reviewer (independent from implementer). Two SoD rules enforce role separation.

charter_dsl: "0.1"
kind: session_charter

metadata:
  charter_id: charter.gov_orchestration
  version: "1.0.0"
  status: accepted
  policy_release: policy.gov.v1
  authored_by: human:governance
  implemented_by: agent:implementer
  ratification:
    hash: sha256:2000abcd00000000000000000000000000000000000000000000000000000002
    signed_at: "2026-05-27T00:00:00Z"

agents:
  - agent_id: agent.orchestrator
    actor_id_source: branch_id
    role: orchestrator
    allowed_models: [provider:model-name]
    allowed_tools: []
    allowed_operations: [delegate, assign_role]
  - agent_id: agent.researcher
    actor_id_source: branch_id
    role: researcher
    allowed_models: [provider:model-name]
    allowed_tools: [tool.search, tool.read_file]
  - agent_id: agent.implementer
    actor_id_source: branch_id
    role: implementer
    allowed_models: [provider:model-name]
    allowed_tools: [tool.read_file, tool.write_file]
  - agent_id: agent.reviewer
    actor_id_source: branch_id
    role: reviewer
    allowed_models: [provider:model-name]
    allowed_tools: [tool.read_file]

registry:
  snapshot: ratification_time
  entries:
    - category: tool
      value: tool.search
      scope: role
      scope_id: researcher
      reason: "Researcher must locate source and documentation."
      evidence_refs: [ev.registry.orchestration.search]
    - category: tool
      value: tool.read_file
      scope: session
      scope_id: session.gov_orchestration
      reason: "Researcher, implementer, and reviewer inspect files."
      evidence_refs: [ev.registry.orchestration.read]
    - category: tool
      value: tool.write_file
      scope: role
      scope_id: implementer
      reason: "Only implementer applies code changes."
      evidence_refs: [ev.registry.orchestration.write]

constraints:
  - constraint_id: gate.registry.all_effects
    description: "Every external effect must resolve through registry policy."
    gate_id: verify_in_registry
    manager_surface: ActionManager
    enforcement: hard
    attach:
      level: class
      tool_class: external_effect
    evidence:
      required: [GateResult]
  - constraint_id: gate.sod.review_independence
    description: "Reviewer must be independent from implementer for the same task."
    gate_id: assert_sod_independence
    manager_surface: ActionManager
    enforcement: hard
    attach:
      level: action
      action: certificate_mint
    evidence:
      required: [SoDCheckEvidence, GateResult]
  - constraint_id: gate.orchestrator.no_task_tools
    description: "Orchestrator coordinates work but does not invoke task tools directly."
    gate_id: deny_direct_tool_call_for_role
    manager_surface: ActionManager
    enforcement: hard
    attach:
      level: action
      action: tool_call
    evidence:
      required: [GateResult]

sod:
  active: true
  rules:
    - rule_id: sod.implementer_reviewer.independent
      conflict_type: audit_independence
      roles: [implementer, reviewer]
      scope: task
      because: "The same actor cannot both implement and approve the same task."
    - rule_id: sod.grant_requester_approver.split
      conflict_type: approval_chain
      roles: [implementer, orchestrator]
      scope: session
      because: "Grant requester and approver must be distinct actors."

permissions:
  default: deny
  resolution:
    specificity_order: [resource, role, tenant, global]
    tie: deny
  allow:
    - rule_id: allow.orchestrator.delegate
      scope: role
      roles: [orchestrator]
      action: delegate
      resources: [session.gov_orchestration]
      requires_evidence: [DelegationEvidence]
      because: "Orchestrator manages work assignment only."
    - rule_id: allow.researcher.search_read
      scope: role
      roles: [researcher]
      action: tool_call
      tools: [tool.search, tool.read_file]
      requires_evidence: [GateResult, ToolCallEvidence]
      because: "Researcher may gather source evidence."
    - rule_id: allow.implementer.read_write
      scope: role
      roles: [implementer]
      action: tool_call
      tools: [tool.read_file, tool.write_file]
      requires_evidence: [GateResult, ToolCallEvidence]
      because: "Implementer may inspect and edit files."
    - rule_id: allow.reviewer.read
      scope: role
      roles: [reviewer]
      action: tool_call
      tools: [tool.read_file]
      requires_evidence: [GateResult, ToolCallEvidence]
      because: "Reviewer may inspect results without editing."
  deny:
    - rule_id: deny.orchestrator.task_tools
      scope: role
      roles: [orchestrator]
      action: tool_call
      tools: [tool.search, tool.read_file, tool.write_file]
      because: "Coordination authority is separate from task execution authority."
    - rule_id: deny.reviewer.write
      scope: role
      roles: [reviewer]
      action: tool_call
      tools: [tool.write_file]
      because: "Reviewer role must remain read-only."

trace:
  stamp: [charter_id, policy_release, agent_id, role, flow_id]
  require_spans:
    - governance.session
    - governance.flow
    - governance.operation
    - sod.check
    - registry.lookup
    - gate.evaluate
    - evidence.emit
    - certificate.state
    - certificate.mint
  require_evidence:
    - GateResult
    - ToolCallEvidence
    - SoDCheckEvidence
    - DelegationEvidence
    - TaskCertificate

Compile targets: one session-level runtime plan, one AgentCharter per agent, shared SoD policy with bidirectional conflict pairs, certificate mint precondition requiring SoD evidence.


Example C: Adapter Boundary Charter

User provides an existing LangGraph compiled graph. Lionagi wraps the invocation boundary. Coarse mode governs graph invocation and emits boundary evidence — internal graph tool calls are not claimed as individually governed.

charter_dsl: "0.1"
kind: agent_charter

metadata:
  charter_id: charter.adapter.langgraph_boundary
  version: "1.0.0"
  status: accepted
  policy_release: policy.gov.v1
  authored_by: human:governance
  implemented_by: agent:adapter_owner
  ratification:
    hash: sha256:3000abcd00000000000000000000000000000000000000000000000000000003
    signed_at: "2026-05-27T00:00:00Z"

agents:
  - agent_id: agent.graph_runner
    actor_id_source: branch_id
    role: adapter_runner
    allowed_models: [provider:model-name]
    allowed_tools: [adapter.langgraph.invoke]

registry:
  snapshot: ratification_time
  entries:
    - category: tool
      value: adapter.langgraph.invoke
      scope: agent
      scope_id: agent.graph_runner
      reason: "The existing compiled graph is governed at the invocation boundary."
      evidence_refs: [ev.registry.adapter.langgraph.invoke]
    - category: path_prefix
      value: /workspace/
      scope: agent
      scope_id: agent.graph_runner
      reason: "Adapter inputs and artifacts are bounded to the workspace."
      evidence_refs: [ev.registry.adapter.langgraph.workspace]

constraints:
  - constraint_id: gate.adapter.boundary_registry
    description: "The adapter invocation must be registered before execution."
    gate_id: verify_in_registry
    manager_surface: ActionManager
    enforcement: hard
    attach:
      level: action
      action: tool_call
      tools: [adapter.langgraph.invoke]
    evidence:
      required: [GateResult, AdapterInvocationEvidence]
  - constraint_id: gate.adapter.input_contract
    description: "Adapter input must pass the graph boundary schema."
    gate_id: validate_adapter_input
    manager_surface: ActionManager
    enforcement: hard
    attach:
      level: action
      action: tool_call
      tools: [adapter.langgraph.invoke]
    evidence:
      required: [GateResult]
  - constraint_id: gate.adapter.no_internal_claim
    description: "Coarse boundary mode must not emit per-internal-tool governed evidence."
    gate_id: assert_boundary_only_evidence
    manager_surface: DataLogger
    enforcement: hard
    attach:
      level: action
      action: evidence_emit
    evidence:
      required: [AdapterInvocationEvidence]

sod:
  active: true
  rules: []

permissions:
  default: deny
  resolution:
    specificity_order: [resource, role, tenant, global]
    tie: deny
  allow:
    - rule_id: allow.adapter_runner.invoke_graph
      scope: role
      roles: [adapter_runner]
      action: tool_call
      tools: [adapter.langgraph.invoke]
      requires_evidence: [GateResult, AdapterInvocationEvidence]
      because: "Adapter runner may invoke the wrapped graph boundary."
  deny:
    - rule_id: deny.adapter_runner.raw_internal_tools
      scope: role
      roles: [adapter_runner]
      action: tool_call
      tools: [tool.internal_graph_tool]
      because: "Internal graph tools are not governed in coarse boundary mode."

trace:
  stamp: [charter_id, policy_release, agent_id, role, adapter_mode]
  require_spans:
    - governance.operation
    - registry.lookup
    - gate.evaluate
    - evidence.emit
  require_evidence:
    - GateResult
    - AdapterInvocationEvidence

Adapter claim rule: boundary mode emits AdapterInvocationEvidence for the invocation boundary and does not emit per-tool governance evidence for internal graph nodes. Fine-grain mode requires translating each internal external-effect tool into a lionagi Tool identity before activation.


6. Integration Points

Runtime Binding

The compiler emits typed runtime targets:

  • AgentCharter objects per agents[*] entry.
  • Registry entries for ToolRegistryPolicy.
  • Gate registrations for each manager surface.
  • SoD rules for SoDPolicy.
  • Permission rules for PolicyResolver.
  • Evidence requirements for evidence emission and certificate minting.
  • Trace expectations for span validation.

Branch integration:

  • Branch carries active charter_id, policy_release, actor identity, and operation context.
  • Branch.operate(middle=...) installs governance through pre-gate, operation context creation, post-result evidence, and span projection.
  • Branch.act() and ActionManager route governed tools through execute_governed().

Session integration:

  • Session pins a policy release and charter set for governed runs.
  • Session.flow() validates role assignments, op limits, artifact contracts, and required control operations against the session charter before execution.
  • Run-end certificate minting occurs after artifact contract verification.

CLI Commands

Note: The li charter and li governance commands are planned as part of the governance build-out and are not yet implemented. The behaviours described below define the intended contract; implementation is tracked in the relevant implementation phases.

li charter validate <path/to/charter.yaml>
li charter compile  <path/to/charter.yaml> --out <path/to/compiled.json>
li governance audit  --session <sess_id>
li governance report --certificate <cert_id>

validate behavior:

  • Performs parse, schema, binding existence, semantic, and safety checks.
  • Exits non-zero on any hard error.
  • Emits warnings for advisory quality issues; accepted charters cannot have unresolved warnings.

compile behavior:

  • Refuses dynamic placeholders, wildcards, unresolved gates, unresolved hooks, and missing trace expectations.
  • Emits normalized JSON plus ratification hash inputs.
  • The normalized artifact (not raw YAML) is the hash input; comments and key ordering do not affect the hash after normalization.

IDE Hints

IDE support comes from a generated JSON Schema artifact:

  • Autocomplete top-level keys in canonical order.
  • Enum completion for kind, status, manager_surface, enforcement, conflict_type, scope values, and canonical trace span names.
  • Diagnostics for missing required blocks and tabs.
  • Diagnostics for both/neither gate_id and hook_name.
  • Warnings for unused registry entries and unreachable permission rules.
  • Hover docs for span names and error codes.

7. Canonical Normalized Output Shape

{
  "charter_id": "charter.simple_reader",
  "charter_dsl": "0.1",
  "kind": "agent_charter",
  "policy_release": "policy.gov.v1",
  "agents": [],
  "registry_entries": [],
  "gate_bindings": [],
  "sod_policy": {},
  "permission_policy": {},
  "evidence_requirements": [],
  "trace_requirements": [],
  "ratification_hash": "sha256:..."
}

The normalized artifact is the hash input. Comments, insignificant whitespace, and key ordering differences inside maps do not affect the hash after normalization.


8. Open Issues For Implementation

  • ADR-0047 must be revised to define DSL-to-runtime binding, not only the Python AgentCharter shape (ADR consolidation phase).
  • ADR-0044 and ADR-0050 must consolidate GateResult to a single owner before implementation begins (ADR consolidation phase).
  • The governance span taxonomy must be extended with permit lifecycle, certificate state transitions, break-glass lifecycle, and soft-gate justification spans before the OTel tracing phase (tracked in standards/trace-naming.md).
  • OperationContext must remain explicit state. A contextvars bridge is allowed for async propagation but must not become the authoritative governance store (OperationContext phase).
  • Boundary adapters must be documented and marketed honestly: they govern invocation boundaries, not hidden internal effects (framework governed endpoints phase).