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¶
- Exact identity, no wildcards. Tool names, registry values, and grant targets must be exact strings. Wildcards are invalid in v0.
- Default deny, deterministic resolution.
permissions.defaultis alwaysdeny. Most-specific scope wins; ties deny. Declaration order does not change authorization. - 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. - 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.
- 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_prefixregistry 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_dslmust be"0.1".metadata.versionis a semver string.metadata.statusis one ofdraft,proposed,accepted,superseded.metadata.ratification.hashis required unless status isdraftorproposed.agentslength is exactly 1 foragent_charter, at least 2 forsession_charter.registry.entries[*].valuehas no wildcards.path_prefixvalues start and end with/.break_glass.expires_afteris at most30min v0.- Hash fields use
sha256:<64-hex-chars>after real compilation. sod.rules[*].roleshas exactly two distinct roles declared inagents.
Semantic Checks¶
- Reject unknown top-level keys.
- Reject duplicate
charter_id,agent_id,constraint_id,rule_idwithin 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_idandhook_namepresent. - Reject constraints with neither binding.
- Reject
hook_namewithouthook_phase. - Reject
attach.level: classwithouttool_class. - Reject
attach.level: actionwithoutaction. - Reject accepted charters whose normalized hash does not match the ratification hash.
- Every
agents[*].allowed_toolsentry must appear in registry entries. permissions.defaultmust bedeny.permissions.resolution.specificity_ordermust be exactly[resource, role, tenant, global].permissions.resolution.tiemust bedeny.- If
sod.activeis false, accepted charters are invalid unless a policy release explicitly allows disabled SoD. - Break-glass
attestation.approver_rolemust not equal the requesting role. - Every required span in
trace.require_spansmust 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:
AgentCharterobjects peragents[*]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:
Branchcarries activecharter_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()andActionManagerroute governed tools throughexecute_governed().
Session integration:
Sessionpins 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 charterandli governancecommands 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_idandhook_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
AgentChartershape (ADR consolidation phase). - ADR-0044 and ADR-0050 must consolidate
GateResultto 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).
OperationContextmust remain explicit state. Acontextvarsbridge 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).