Reference
ChatMD language reference
The complete declaration syntax for agent definitions.
View Markdown source ↗For current native/daemon hosting, see host modes and agent-host orchestration. Daemon work belongs to the session actor, not a connected UI. The existing language/tool APIs remain shared; file-backed session/controller descriptions should be read in that host context. Instruction helper compatibility names emit developer-role messages.
ChatMarkdown (ChatMD) is a small, closed XML vocabulary embedded in Markdown for authoring LLM conversations as plain files.
The core idea is simple: a ChatMD file is both:
- a prompt (model config, tool permissions, instructions, context), and
- an auditable transcript (tool calls + tool outputs + assistant replies).
This makes workflows reproducible, diffable, and easy to review.
“What the model sees” (and the explicit exceptions)
Section titled ““What the model sees” (and the explicit exceptions)”Ochat tries hard to ensure the model sees exactly what’s in your ChatMD document, but there are a few explicit, documented mechanisms that transform the file:
- HTML comments are stripped:
<!-- ... -->is removed before parsing and never reaches the model. <import/>expands: selected<import src="..."/>directives are replaced with the contents of the referenced file at parse time.- Runtime declarations stay host-managed: top-level
<script>,<shell_access>, and<moderator_runtime>declarations are parsed, validated, and executed by the host. They are not converted into model-visible request history. - RAW blocks disable parsing:
RAW| ... |RAWis treated as literal text (no tag parsing inside). - Optional meta-refine preprocessing: if enabled, the prompt may be rewritten before parsing (see “Meta-refine” below).
Everything else is intentionally boring: there are no hidden templates, implicit tool permissions, or “magic” side channels.
1) What is valid ChatMD?
Section titled “1) What is valid ChatMD?”ChatMD is not general HTML/XML. It recognises a closed set of lowercase tag names.
1.0 Official tag set
Section titled “1.0 Official tag set”These are the tag names ChatMD recognises (lowercase, case-sensitive):
- Message / transcript structure:
msg,user,assistant,system,developer - Host-managed runtime declarations:
script,shell_access,moderator_runtime - Tools / tool trace:
tool,tool_call,tool_response - Inline helpers:
doc,img,agent,import - Reasoning:
reasoning,summary - Configuration:
config
Inside <shell_access> and <tool type="shell">, the lexer additionally
recognizes a strict nested shell vocabulary for capabilities, resolver,
environment, limits, backends, policy/matchers, approvals/reviewers,
interceptors, effect analysis, secrets, audit, and fixed-command children.
These nodes are built by the normal ChatMD parser and validated by the shell
declaration layer. Unknown shell children/attributes are errors rather than
literal message text.
1.1 Top-level rule (important)
Section titled “1.1 Top-level rule (important)”A ChatMD document must be a sequence of recognised ChatMD elements at the top level.
- ✅ Allowed at top-level:
<user>...</user>,<tool .../>,<config .../>, etc. - ✅ Whitespace between top-level elements is allowed.
- ❌ Plain text at top-level is an error.
- ❌ Unknown tags at top-level are an error (because unknown tags are treated as literal text).
Example:
<!-- ✅ valid --><user>Hello</user>
<!-- ❌ invalid: top-level text -->Hello1.2 Unknown tags
Section titled “1.2 Unknown tags”Unknown markup is preserved as literal text inside recognised elements. For example:
<user>Hello <b>world</b></user><b> is not a ChatMD tag, so it is preserved as literal text and passed through to the model.
1.3 Case sensitivity
Section titled “1.3 Case sensitivity”Tags are case-sensitive. Use lowercase:
- ✅
<user>...</user> - ❌
<User>...</User>(treated as unknown text; may break top-level validity)
1.4 Attribute syntax (practical rules)
Section titled “1.4 Attribute syntax (practical rules)”- Flag attributes are supported and mean “present = true”, e.g.
<doc src="x" local/>. - Attributes can be quoted with single or double quotes.
- Quoted values support backslash-escaped quotes.
- A small set of HTML entities are decoded in attribute values:
&,<,>,",'.
2) RAW blocks (highly recommended)
Section titled “2) RAW blocks (highly recommended)”RAW blocks let you embed arbitrary text without the ChatMD lexer interpreting <tags> inside it.
Syntax:
- opener:
RAW| - terminator:
|RAW
Everything between them is treated as literal text.
This is the #1 way to embed:
- JSON tool arguments / results
- code containing
</>or XML-like snippets - “literal ChatMD” examples inside a message
Example:
<user>Here is JSON (no escaping needed):RAW|{ "path": "README.md", "offset": 0 }|RAW</user>3) Core elements (top-level)
Section titled “3) Core elements (top-level)”These elements form the “program” and the transcript.
3.1 Minimal skeleton
Section titled “3.1 Minimal skeleton”The smallest useful ChatMD file is:
<config model="gpt-4o" temperature="0"/>
<user>Hello.</user>3.2 Conversation structure elements
Section titled “3.2 Conversation structure elements”| Element | Purpose | Notes / key attributes |
|---|---|---|
<config .../> | Model and generation parameters | Optional. If multiple appear, the first <config/> wins. Flag attribute: show_tool_call. |
<tool .../> | Declare tools available to the assistant | Builtin, long/compact shell, agent-backed, or MCP-backed. |
<user>...</user> | User message | The most common input block. |
<assistant>...</assistant> | Assistant message | Usually written by ochat into the transcript. Often uses RAW blocks for faithful round-tripping. |
<system>...</system> | System message | High-priority instructions. |
<developer>...</developer> | Developer message | Mid-priority instructions (below system). |
<msg role="...">...</msg> | Generic message form | Escape hatch for less common roles/legacy. Roles supported by the runtime: user, assistant, system, developer, tool. |
<tool_call ...>...</tool_call> | Tool invocation record | Typically written by ochat; see “Tool calls & tool responses”. |
<tool_response ...>...</tool_response> | Tool output record | Typically written by ochat; see “Tool calls & tool responses”. |
<reasoning ...>...</reasoning> | Reasoning record | Typically written by reasoning-capable models; requires id if authored manually. |
<script ...>...</script> / <script ... src="..." /> | Host-managed ChatML script | Top-level only. Supports moderator and shell extension kinds. |
<shell_access ...>...</shell_access> | Named shell runtime | Strict host-only configuration; never model history. |
<moderator_runtime shell_runtime="..."/> | Moderator process binding | Routes Process.run through a named shell runtime. |
3.3 Inline content helpers (only inside message bodies)
Section titled “3.3 Inline content helpers (only inside message bodies)”These tags are recognised by the parser, but they are primarily meaningful inside message bodies (e.g. inside <user>...</user>):
| Inline tag | What it does |
|---|---|
<doc src="..." [local] [strip] [markdown] /> | Inline document text (local file or remote URL). |
<img src="..." [local] /> | Inline an image (remote URL or local file encoded as a data URI). |
<agent src="..." [local]> ... </agent> | Run another ChatMD prompt as a sub-agent and insert its final answer. |
<import src="..."/> | Parse-time include (only expands in certain places; see below). |
3.4 <script> moderation declarations
Section titled “3.4 <script> moderation declarations”<script> declares a host-managed ChatML program. The script is retained in
the typed prompt model, but it is not sent to the model as a message.
Every script uses language="chatml" and a unique ID. Supported kinds are
moderator, shell_matcher, shell_reviewer,
shell_before_interceptor, shell_after_interceptor,
shell_effect_analyzer, and shell_audit_filter. A prompt may contain many
shell scripts, but at most one conversation moderator is selected.
Supported forms:
<script language="chatml" kind="moderator" id="main">... ChatML source ...</script><script language="chatml" kind="moderator" id="main" src="moderator.chatml" />Validation rules:
src="..."and inline body text are mutually exclusive.src="..."is loaded during prompt parsing, so missing files fail early.- Relative
srcpaths resolve against the directory of the source file declaring the script. A script in an imported ChatMD file therefore loads relative to that imported file, not the root prompt directory. - Duplicate script IDs are errors even when kinds differ.
- More than one selected moderator script is an error.
- Extra attributes are rejected.
If a script lives in a separate file, the parsed prompt retains both the src
path and the loaded source text so later compilation can report the original
location clearly.
3.5 Host-managed moderation lifecycle
Section titled “3.5 Host-managed moderation lifecycle”When a prompt includes a moderator <script>, ochat keeps the script
host-managed:
- the script is parsed and validated with the rest of the prompt,
- it is compiled once per prompt load,
- each chat session gets a fresh runtime instance,
- resumed sessions restore only the serializable moderator snapshot,
- the script itself is never turned into a model-visible message.
Prompts without a <script> keep the baseline behavior. The shared drivers,
chat_tui, export flow, and nested run_agent path all fall back to the
ordinary non-moderated request assembly.
The shared moderation vocabulary defines these phase names:
session_startsession_resumeturn_startmessage_appendedpre_tool_callpost_tool_responseturn_endinternal_event
In ChatML scripts, the event constructor exposed for appended canonical items is
Item_appended(item). The host phase name remains message_appended, but
script authors should pattern-match on Item_appended.
Built-in drivers currently emit:
session_startfor new moderated sessions,session_resumewhen restoring a persisted moderator snapshot,turn_startbefore each model request,message_appendedafter a canonical transcript item is appended during the streamed runtime,pre_tool_callbefore a tool executes,post_tool_responseafter a tool output item is produced,turn_endafter a streamed turn completes,internal_eventwhile replaying queued moderator events FIFO.
For moderated chat_tui sessions, this lifecycle is split across three host
layers:
Moderator_managerowns durable moderator state, overlay state, halted state, and queued internal events.In_memory_streamowns one active model/tool turn and handles explicit safe points such as turn start, post-tool-result, and turn end.chat_tuiowns the session controller that reacts to idle wakeups, defers wakeups during active turns, refreshes the visible transcript from effective history at safe points, and starts follow-up turns only when the UI is idle.
The visible transcript shown by chat_tui is not raw canonical history. It is
the current canonical history projected through the moderator overlay. During
streaming the UI may still apply token and tool patches directly for
responsiveness, but safe-point refreshes reproject the visible transcript from
effective history after moderation-visible changes.
The current safe points are:
- startup / resume moderation,
- turn-start request preparation,
- post-tool-result continuation,
- turn end,
- idle/background moderator drains in
chat_tui, - compaction completion in
chat_tui.
3.5.1 Runtime requests (host-visible)
Section titled “3.5.1 Runtime requests (host-visible)”Moderator scripts can emit host-visible runtime requests:
Runtime.request_turn()requests that the host run one more ordinary model turn after the current turn completes.- It is interpreted after
turn_endhandling finishes. - Multiple requests collapse to a single continuation decision.
Runtime.end_session(...)overridesrequest_turn.- In
chat_tui, the same request can also schedule an idle follow-up turn after startup work or an idle internal-event drain, as long as no turn is already active.
- It is interpreted after
Runtime.request_compaction()requests that the embedding compact history (if supported).- This request does not itself force another model turn.
Runtime.end_session(reason)requests session termination.
The shared drivers interpret these requests through an explicit runtime-semantics policy layer.
3.5.1a Deferred steering notes and safe-point input
Section titled “3.5.1a Deferred steering notes and safe-point input”In the older file-backed chat_tui host, when the user submits steering text
while a turn is already streaming, the host does not inject a new canonical user message into the
in-flight request. Instead it records a deferred steering note in
session-controller state.
That deferred steering note is applied only at the next safe model-input boundary. Concretely:
- it remains outside canonical transcript history,
- it survives until the next safe-point request preparation,
- it is appended as transient developer input for that request only,
- it never rewrites tool output history in place.
This preserves the in-flight reasoning/tool workflow while still letting the user steer the next request. Native-local and daemon hosts instead admit canonical deferred entries through the actor; see the host-specific steering contract.
3.5.2 Model recipes (Model.call / Model.spawn)
Section titled “3.5.2 Model recipes (Model.call / Model.spawn)”Moderator scripts can call host-registered model recipes:
Model.call(recipe_name, payload_json)runs a host-defined recipe and returns structured JSON.Model.spawn(recipe_name, payload_json)starts a background job and returns a stable job id immediately.
Spawned model jobs deliver completion back to the moderator as internal events, using stable v1 tags:
Model_job_succeeded(job_id, recipe_name, result_json)Model_job_failed(job_id, recipe_name, message)
Note: the initial implementation may track spawned jobs in memory only; in-flight jobs are not durably persisted across process restarts.
Moderator scripts receive ctx.items, where each item has the structural
shape:
type item = { id : string ; value : json }The moderator surface also installs an Item helper module for common
structured-item operations:
Item.create(id, value)Item.id(item)Item.value(item)Item.kind(item)Item.role(item)Item.text_parts(item)Item.input_text_message(id, role, text)Item.output_text_message(id, text)
Turn.append_item, Turn.replace_item, and Turn.delete_item are the
preferred item-oriented mutation names. The older *_message names remain as
aliases.
3.6 Overlay, tool moderation, and persistence semantics
Section titled “3.6 Overlay, tool moderation, and persistence semantics”Moderator scripts do not rewrite canonical OpenAI history in place. Instead the host keeps a durable overlay that can:
- prepend synthetic developer messages (the compatibility operation is named
prepend_system), - append synthetic items,
- replace projected items by id,
- delete projected items by id,
- halt the session with an explicit reason.
That overlay is applied before the next moderated model turn to produce the effective request history. Export and preview flows materialize synthetic overlay entries explicitly so the saved ChatMD transcript stays human-readable.
Tool moderation is also host-managed:
Tool.approve()keeps the call as-is,Tool.reject("reason")skips execution and synthesizes a tool output item,Tool.rewrite_args(json)changes the tool payload deterministically,Tool.redirect("other_tool", json)rewrites both tool name and args.
After every successful moderation event, the host replays queued internal
events FIFO through phase internal_event, with a safety limit to avoid
unbounded loops. Spawned model job completions (Model.spawn) are delivered using this same internal-event replay mechanism.
In chat_tui, queued internal events can surface while the UI is otherwise
idle. Background producers may enqueue work and register a wakeup callback; the
session controller drains that queued work at an idle safe point, refreshes the
visible transcript if overlay state changed, and may schedule a follow-up turn
when the moderator requests one.
An end-to-end idle async completion currently looks like this:
- a moderator script calls
Model.spawn(...) - the background job finishes and is converted into
Model_job_succeeded(...)orModel_job_failed(...) - the host enqueues that internal event into
Moderator_manager - the idle
chat_tuireducer receives a moderator wakeup chat_tuidrains queued internal events, refreshes visible transcript state from effective history, and applies any notices or halted state updates- if the moderator emitted
Runtime.request_turn(),chat_tuistarts one more ordinary turn from the current session state
An end-to-end non-interrupting steering flow currently looks like this:
- the assistant is already in a streamed turn or tool workflow
- the user submits steering text
chat_tuirecords a deferred steering note instead of appending a canonical user message mid-turn- the current turn reaches a safe point and eventually completes
- the next request is prepared from moderator-effective history
- the deferred steering note is appended as transient developer input for that next request only
Persisted moderator state is intentionally narrow:
- current script state,
- queued internal events,
- halted flag,
- host overlay snapshot,
- script id and source hash used to validate restores.
Only data-shaped ChatML values cross that persistence boundary. Closures, refs, builtins, modules, and tasks are rejected instead of being serialized.
4) <config/>
Section titled “4) <config/>”<config/> controls model selection and generation parameters that ochat currently wires through.
Supported attributes:
model="..."(string)max_tokens="..."(int)temperature="..."(float)reasoning_effort="..."(string; interpreted by the OpenAI client)id="..."(string label; optional)show_tool_call(flag attribute; presence enables inline tool payloads)
Example:
<config model="gpt-4o" temperature="0.2" max_tokens="1024"/>4.1 show_tool_call (inline vs externalised tool payloads)
Section titled “4.1 show_tool_call (inline vs externalised tool payloads)”When show_tool_call is present, ochat persists tool arguments/results inline using RAW blocks.
When absent (default), large tool payloads are written to ./.chatmd/*.json and referenced via <doc .../>.
See the “Tool calls & tool responses” section for exact layouts.
5) <tool/> declarations (capabilities)
Section titled “5) <tool/> declarations (capabilities)”<tool/> declarations define what actions the assistant is allowed to take.
ChatMD supports five tool “shapes”:
5.1 Built-in tools
Section titled “5.1 Built-in tools”<tool name="read_file"/><tool name="apply_patch"/>read_file also has a structured built-in form for declaring named readable
roots:
<tool name="read_file" description="Read project source and documentation."> <read id="source" path="lib" description="Relative to ochat's launch directory"/> <read id="docs" path="${workspace}/docs-src" description="Project docs"/></tool>The self-closing form defaults to one cwd root at ${tool_dir}. Relative
<read path="..."/> values also use ${tool_dir}. The model-visible
read_file schema gains an optional root enum containing the declared IDs,
and its generated description includes every resolved root path and root
description. A custom tool description is appended to that generated usage
guidance.
Calls with root use a path relative to that named root:
{"root":"docs","file":"overview/tools.md","offset":0,"line_count":100}Without root, relative paths resolve from the ochat launch directory.
Relative and absolute requests are accepted only when the canonical target is
inside a configured root. Targets must be existing regular text files.
Explicit <read id="computer" path="/"/> grants host-wide read access subject
to operating-system permissions. See the tools reference
for complete semantics and security guidance.
5.2 Configured read_file roots
Section titled “5.2 Configured read_file roots”Each nested <read/> has these attributes:
| Attribute | Required | Meaning |
|---|---|---|
id | yes | Unique model-visible root selector. |
path | yes | Existing directory path or standard path expression. |
description | no | Usage guidance included in the tool description sent to the model. |
The parent <tool name="read_file"> accepts an optional description. The
compatibility name get_contents accepts the same structure but still exposes
the model-visible function name read_file.
Path variables: ${workspace} and source directories
Section titled “Path variables: ${workspace} and source directories”The standard variables accepted in path are ${workspace}, ${tool_dir},
${prompt_dir}, ${source_dir}, ${session_dir}, ${cache_dir}, and
${home}. Their values are supplied by the execution host. In native local and
daemon hosts, ${workspace} is the selected workspace, while ${tool_dir}
retains the host launch directory unless an embedder supplies it explicitly.
Captured prompts use materialized source directories for ${prompt_dir} and
${source_dir}. See workspaces and paths
for the complete native-host contract, and the batch command guide
for file-backed execution context. Unknown variables are fatal. All roots must
resolve to existing directories before the first model request.
read_file calls accept file, optional root, optional non-negative
offset, and optional non-negative line_count. Root paths and requested
files are canonicalized before confinement checks, so .. and symlinks
cannot escape the declared roots. The full runtime behavior is documented in
the tools reference.
5.3 Shell tools (wrap trusted commands)
Section titled “5.3 Shell tools (wrap trusted commands)”<tool name="rg" command="rg" description="ripgrep search"/>This compact form is desugared into a fixed shell tool. Full shell tools bind to a named runtime and support fixed, structured, conservative chain, raw, and script-file modes:
<shell_access id="development" extends="builtin:workspace-development@1"/><tool name="shell" type="shell" mode="structured" runtime="development"/>The runtime is compiled and authorized before the tool is exposed. See the shell runtime reference and shell tool reference.
5.4 Agent-backed tools (prompt-as-tool)
Section titled “5.4 Agent-backed tools (prompt-as-tool)”<tool name="triage" agent="prompts/triage.chatmd" local description="Triage a bug report"/>5.5 MCP-backed tools (import tools from an MCP server)
Section titled “5.5 MCP-backed tools (import tools from an MCP server)”<tool mcp_server="https://tools.acme.dev" includes="weather,stock_ticker" strict/>5.6 Validation rules (important)
Section titled “5.6 Validation rules (important)”- Legacy non-long-form declarations use exactly one of
command,agent, ormcp_server. Long-form shell tools usetype="shell",mode,runtime, and mode-specific attributes/children. - For builtin/shell/agent tools,
name="..."must be non-empty. - Configured
read_fileroots require non-empty, uniqueidvalues and an existing directorypath. Only nested<read/>elements are accepted. - For MCP tools:
name="..."selects a single tool name, orinclude="a,b"/includes="a,b"selects a comma-separated list, or- neither means “no filter” (implementation-dependent; typically exposes the server’s tool catalog).
For a deeper tool reference (built-ins, schemas, and examples), see:
docs-src/overview/tools.md.
6) Messages: <user>, <assistant>, <system>, <developer>, <msg>
Section titled “6) Messages: <user>, <assistant>, <system>, <developer>, <msg>”Most prompts use the shorthand message tags:
<system>You are careful and cite sources.</system>
<user>Read README.md and propose a patch.</user><msg role="..."> exists as an escape hatch:
<msg role="user">Hello</msg>Supported roles in the runtime conversion are:
user, assistant, system, developer, tool.
7) Tool calls & tool responses (<tool_call>, <tool_response>)
Section titled “7) Tool calls & tool responses (<tool_call>, <tool_response>)”These elements represent the on-disk execution trace of tool usage.
They are usually written by ochat, not hand-authored.
7.1 Required attributes
Section titled “7.1 Required attributes”Tool calls:
<tool_call function_name="read_file" tool_call_id="call_123">...</tool_call>Tool responses:
<tool_response tool_call_id="call_123">...</tool_response>There is also a special round-tripping convention for “custom tool calls”:
<tool_call type="custom_tool_call" ...><tool_response type="custom_tool_call" ...>
7.2 Persistence mode A: inline payloads (show_tool_call)
Section titled “7.2 Persistence mode A: inline payloads (show_tool_call)”When show_tool_call is enabled, ochat persists payloads inline:
<tool_call tool_call_id="call_123" function_name="read_file" id="item_456">RAW|{ "path": "README.md" }|RAW</tool_call>
<tool_response tool_call_id="call_123">RAW|...tool output...|RAW</tool_response>7.3 Persistence mode B: externalised payloads (default)
Section titled “7.3 Persistence mode B: externalised payloads (default)”When show_tool_call is not set, payloads are written to ./.chatmd/ and referenced:
<tool_call function_name="read_file" tool_call_id="call_123" id="item_456"> <doc src="./.chatmd/0.tool-call.call_123.json" local/></tool_call>
<tool_response tool_call_id="call_123"> <doc src="./.chatmd/0.tool-call-result.call_123.json" local/></tool_response>This keeps the main transcript readable even when tools exchange large JSON payloads.
8) Inline content helpers (the “power tools” inside messages)
Section titled “8) Inline content helpers (the “power tools” inside messages)”8.1 <doc src="..." .../> — inline documents (local or remote)
Section titled “8.1 <doc src="..." .../> — inline documents (local or remote)”<user>Summarise this:<doc src="README.md" local/></user>Attributes:
src="..."requiredlocal(flag): read from disk instead of HTTPstrip(flag): if the doc is HTML, strip markup and collapse whitespace into readable textmarkdown(flag): convert HTML to Markdown (local file or remote URL)
Precedence:
- If
stripis present, it takes precedence overmarkdown.
Local path resolution:
- Local docs are resolved against the prompt directory first; if still relative and not found, the process CWD is also consulted.
8.2 <img src="..." [local]/> — inline images
Section titled “8.2 <img src="..." [local]/> — inline images”<user>What’s wrong with this UI?<img src="assets/screenshot.png" local/></user>If local is present, the image is encoded as a data URI before being sent to the API.
8.3 <agent src="..." [local]> ... </agent> — call a sub-agent
Section titled “8.3 <agent src="..." [local]> ... </agent> — call a sub-agent”An <agent> runs another ChatMD prompt and substitutes its final answer inline.
Example (summarise a document using a specialised agent prompt):
<user>Here’s a summary produced by my sub-agent:<agent src="prompts/summarise.chatmd" local> <doc src="README.md" local strip/></agent></user>Notes:
- Any children of
<agent>become the sub-agent’s runtime input. - Results are cached (so repeated identical agent calls can be cheap).
9) <import src="..."/> — parse-time include (modularity)
Section titled “9) <import src="..."/> — parse-time include (modularity)”<import/> keeps prompts maintainable by letting you reuse top-level
declarations and shared message text (policies, glossaries, style guides).
Where imports expand
Imports are expanded recursively at the document top level and inside:
<user>...</user><system>...</system><developer>...</developer><agent>...</agent><msg role="user|system|developer">...</msg>
Where imports do not expand
Inside other elements, <import/> is preserved as literal text (for example
inside <assistant>, <tool_call>, <tool_response>, <reasoning>).
Relative src paths resolve against the importing source file’s directory.
Top-level imports can supply tools, scripts, and shell-runtime declarations;
those declarations retain the imported source context. The optional
namespace attribute qualifies imported declarations; see
IDs, references, and namespaces.
Import cycles and duplicate sibling namespace aliases are rejected.
Native-local and daemon hosts additionally pin these sources into an artifact
tree. Imports/scripts must remain beneath the root prompt directory; parent
traversal outside it and absolute import/script paths are rejected during
artifact construction, even if standalone parsing succeeds. Runtime
${prompt_dir}/${source_dir} refer to that captured tree, not the original
source directories. See pinning and paths.
For example, a top-level declaration bundle can be imported with:
<import src="runtime/common.chatmd" namespace="team"/>For shared message text:
<system><import src="policies/safety.md"/></system>10) Meta-refine (optional preprocessing)
Section titled “10) Meta-refine (optional preprocessing)”If enabled, ochat can run a “meta-refine” pass before parsing ChatMD.
Enable it via:
- environment variable
OCHAT_META_REFINE(truthy values like1,true,yes,on), or - placing the marker comment
<!-- META_REFINE -->anywhere in the prompt.
11) Common errors and gotchas
Section titled “11) Common errors and gotchas”- Top-level text is forbidden: wrap content in
<user>...</user>, etc. - Unknown tags at top-level fail: unknown markup becomes literal text and triggers the top-level text rule.
- Mismatched tags fail:
<user>...</assistant>is an error. - Unterminated quoted attribute values fail: e.g.
alt="...without closing quote. - Unterminated RAW blocks fail:
RAW| ... |RAWmust be closed. - Tag names are lowercase and case-sensitive.
- Shell configuration is strict: unknown/duplicate runtime sections, unresolved references, import/inheritance cycles, unsupported features or profiles, missing required backends, rejected manifests, and administrative ceiling violations fail before dependent tools are published. There is no fallback to direct execution.
For the complete shell grammar and diagnostics, see ChatMD shell runtime reference.