{"id":30063896,"url":"https://github.com/dakotamurphyucf/ochat","last_synced_at":"2026-06-23T13:32:59.110Z","repository":{"id":257185619,"uuid":"857558474","full_name":"dakotamurphyucf/ochat","owner":"dakotamurphyucf","description":"Text-first toolkit for building reproducible, composable LLM workflows as plain files in OCaml.","archived":false,"fork":false,"pushed_at":"2026-04-24T03:28:00.000Z","size":79376,"stargazers_count":27,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-04-24T05:29:30.553Z","etag":null,"topics":["agent-workflows","ai-agents","cli","developer-tool","developer-tools","llm","mcp","ocaml","openai","prompt-engineering","retrieval-augmented-generation","tool-calling","tui","vector-search"],"latest_commit_sha":null,"homepage":"https://dakotamurphyucf.github.io/ochat/","language":"OCaml","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dakotamurphyucf.png","metadata":{"files":{"readme":"Readme.md","changelog":"history.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-09-15T01:04:09.000Z","updated_at":"2026-04-24T03:28:05.000Z","dependencies_parsed_at":"2024-09-15T07:36:29.955Z","dependency_job_id":"3c1eee46-3c4d-464f-b6cb-12d45e54bea7","html_url":"https://github.com/dakotamurphyucf/ochat","commit_stats":null,"previous_names":["dakotamurphyucf/ocaml-gpt","dakotamurphyucf/ochat"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/dakotamurphyucf/ochat","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dakotamurphyucf%2Fochat","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dakotamurphyucf%2Fochat/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dakotamurphyucf%2Fochat/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dakotamurphyucf%2Fochat/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dakotamurphyucf","download_url":"https://codeload.github.com/dakotamurphyucf/ochat/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dakotamurphyucf%2Fochat/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34691864,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-23T02:00:07.161Z","response_time":65,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agent-workflows","ai-agents","cli","developer-tool","developer-tools","llm","mcp","ocaml","openai","prompt-engineering","retrieval-augmented-generation","tool-calling","tui","vector-search"],"created_at":"2025-08-08T04:49:14.131Z","updated_at":"2026-06-23T13:32:59.101Z","avatar_url":"https://github.com/dakotamurphyucf.png","language":"OCaml","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Ochat – text-first toolkit for custom AI agents, LLM workflows, and vector search\n\n**Build custom AI agents and scripted LLM workflows as plain text files.**\n\nOchat is an **OCaml toolkit** for building **reproducible, composable, tool-using LLM workflows** without locking the workflow into a single UI or heavyweight framework.\n\nInstead of hiding prompts, tool permissions, transcript state, and orchestration inside an application, Ochat keeps them in **static, diffable files** that you can version-control, review, branch, and run in different hosts.\n\nIf you like tools like Claude Code or Codex, Ochat operates at a more fundamental level: it gives you the building blocks to create **your own prompt packs, agents, and workflow systems**.\n\n\u003cdiv\u003e\n\u003ca href=\"https://asciinema.org/a/gIelV4eAeA0LKvG7\" target=\"_blank\"\u003e\u003cimg height=\"700\" width=\"900\" src=\"https://asciinema.org/a/gIelV4eAeA0LKvG7.svg\" /\u003e\u003c/a\u003e\n\u003c/div\u003e\n\n---\n\n## Contents\n\n- [Why Ochat exists](#why-ochat-exists)\n- [Design Principles](#design-principles)\n- [Ochat in one minute](#ochat-in-one-minute)\n- [What is Ochat?](#what-is-ochat)\n- [What makes Ochat different?](#what-makes-ochat-different)\n- [How Ochat compares](#how-ochat-compares)\n- [Who Ochat is for](#who-ochat-is-for)\n- [Quick start](#quick-start)\n- [What can I do with Ochat?](#what-can-i-do-with-ochat)\n- [Common use cases](#common-use-cases)\n- [First 10 minutes with Ochat](#first-10-minutes-with-ochat)\n- [Example ChatMD prompts](#example-chatmd-prompts)\n- [Build from source](#build-from-source-ocaml)\n- [Core concepts](#core-concepts)\n- [Architecture overview](#architecture-overview)\n- [Documentation](#documentation)\n- [OCaml integration](#ocaml-integration)\n- [Future directions](#future-directions)\n- [Project status](#project-status--expect-rapid-change)\n\n---\n\n## Why Ochat exists\n\nMost LLM tools today either:\n- hide workflows inside polished UIs,\n- require you to rebuild everything in a code-first framework, or\n- make prompts and agent state hard to inspect, reproduce, and evolve.\n\nOchat exists to make LLM workflows feel more like **engineering artifacts**.\n\nWith Ochat, prompts, tools, transcript state, and orchestration live in plain text files that you can:\n- **version-control**\n- **diff and review**\n- **branch and resume**\n- **compose into larger workflows**\n- **run in the terminal, scripts, CI, or over MCP**\n\nThe goal is simple: make agent workflows **explicit, inspectable, portable, and reproducible**.\n\n---\n\n## Design principles\n\nOchat is built around a few core principles:\n\n- **Everything important should be inspectable**\n- **Workflows should be versionable**\n- **Agent runs should be reproducible**\n- **Tools should be explicit**\n- **Custom workflows should not depend on a single UI**\n- **Advanced orchestration should remain auditable**\n\n---\n\n## Ochat in one minute\n\n- A workflow is usually a `.md` file written in **ChatMarkdown (ChatMD)**.\n- That file can contain the prompt, model config, tool permissions, transcript, and execution artifacts.\n- The same workflow can run in the TUI, the CLI, or over MCP.\n- Workflows can call tools, other workflows, and an optional host-managed **ChatML** script.\n- Because everything is stored as text, runs are diffable, reproducible, resumable, and easy to version-control.\n\n---\n\n## What is Ochat?\n\nOchat is a toolkit for building **agent workflows and orchestrations as static files**.\n\nIts core format is **ChatMarkdown (ChatMD)**, a Markdown + XML dialect for defining and running agents. A single ChatMD file can contain:\n\n- model and generation parameters\n- tool declarations and permissions\n- developer and user instructions\n- the full conversation history\n- tool calls and tool results\n- imported artifacts such as documents or images\n- optional host-managed scripting for orchestration and moderation\n\nIn Ochat, an agent is often just a `.md` file.\n\nThat file is not only a prompt — it can also be:\n- the execution log of a run,\n- a reusable workflow component,\n- a tool callable by another agent,\n- a portable artifact you can inspect and refine over time.\n\nBecause everything is captured in text files, workflows become:\n\n- **reproducible** – the exact config and transcript are version-controlled\n- **diffable** – reviews show exactly what changed and what the model did\n- **composable** – workflows can call other workflows (prompt-as-tool)\n- **portable** – prompts are plain text, not locked into one interface\n- **editable** – use any text editor, IDE, or terminal workflow\n- **LLM-friendly** – the XML structure is easy for models to parse and generate\n\nThe same `.md` workflow definition can be executed in multiple hosts:\n\n- the **terminal UI** (`chat_tui`) for interactive work\n- **scripts and CI** via `ochat chat-completion`\n- a **remote MCP server** via `mcp_server`, so IDEs and other applications can call agents over stdio or HTTP/SSE\n\nThe ChatMD language provides a rich set of features for prompt engineering in a modular way, supporting workflows from simple prompts to more advanced orchestrations. See the [language reference](docs-src/overview/chatmd-language.md).\n\nChatMD prompts can also embed a single host-managed **ChatML** moderation script:\n\n- declare it with `\u003cscript language=\"chatml\" kind=\"moderator\" ...\u003e`\n- keep it invisible to the model request itself\n- prepend, append, replace, or delete effective transcript items\n- inspect and construct structured items through the `Item` builtin module\n- moderate tool calls by approving, rejecting, rewriting, or redirecting them\n- persist only serializable runtime state between resumed sessions\n- request another ordinary model turn after `turn_end` via `Runtime.request_turn()`\n- schedule idle follow-up turns after background internal events or startup work\n- orchestrate additional model-backed work via `Model.call` and `Model.spawn`\n- defer user steering submitted during streaming until a safe model-input boundary\n\nPrompts without a `\u003cscript\u003e` keep the baseline behavior: the shared drivers, `chat_tui`, export flow, and nested agent execution continue to work without the moderation layer.\n\nOchat is implemented in **OCaml**, but the workflows themselves are **language-agnostic**. Tools exchange JSON, prompts are plain files, and the system makes no assumptions about the kinds of applications your workflows target.\n\n\u003e **Current provider support:** nativley uses OpenAI Responses API format.  \n\u003e The architecture is designed to support additional providers in the future.\n\u003e To use other providers right now you can use a [proxy-server](https://docs.litellm.ai/) and set the enviorment url API_URL to the proxy url\n\n---\n\n## What makes Ochat different?\n\nOchat is not just:\n- a coding assistant,\n- a prompt playground,\n- an orchestration framework,\n- or an MCP wrapper.\n\nIt is a **text-first workflow toolkit** built around a few core ideas:\n\n- **Prompt-as-program**  \n  A workflow can live in a single file that contains config, tools, transcript state, and execution artifacts.\n\n- **Transcript-as-artifact**  \n  Runs are inspectable, diffable, branchable, and resumable.\n\n- **Prompt packs instead of fixed apps**  \n  Build your own Claude Code/Codex-style workflows instead of being limited to a single built-in agent UX.\n\n- **Composable agents**  \n  Mount one prompt as a tool inside another workflow.\n\n- **Host-managed control logic**  \n  Use ChatML scripts to moderate tool calls, manage workflow state, and orchestrate multi-step behavior.\n\n- **Run anywhere**  \n  Use the same workflow in the TUI, the CLI, or through MCP.\n\n---\n\n## How Ochat compares\n\nOchat occupies a different niche in the LLM tooling landscape than most agent tools.\n\n### Compared to coding-agent products\nTools like **Claude Code**, **Codex-style CLIs**, or **Aider** are polished agent applications for working in a repo.\n\nOchat is more fundamental: it is a **toolkit for building your own agent workflows** as plain files. Instead of shipping a single hard-coded agent experience, Ochat lets you define prompts, tools, transcript state, and orchestration explicitly.\n\n### Compared to orchestration frameworks\nFrameworks like **LangGraph** and similar Python agent stacks are typically **code-first**: you build workflows in application code.\n\nOchat is **artifact-first**: workflows live as **text files** that can be version-controlled, diffed, composed, resumed, and run in different hosts.\n\n### Compared to observability / prompt-management platforms\nPlatforms like **LangSmith**, **PromptLayer**, or **Humanloop** focus on tracing, evaluation, and hosted prompt management.\n\nOchat focuses on **authoring and running workflows locally as inspectable artifacts**. It is not primarily a dashboard or hosted prompt registry.\n\n### Compared to MCP tools\nMCP provides a protocol for exposing tools and resources to models and clients.\n\nOchat supports MCP, but it is broader than that. It is a **workflow system** with:\n- ChatMD prompt files\n- transcript persistence\n- prompt-as-tool composition\n- host-managed scripting\n- TUI and CLI execution\n- local indexing and retrieval tools\n\n### The simplest way to think about it\nIf other tools are:\n- **agent apps**\n- **Python orchestration frameworks**\n- **hosted observability platforms**\n- or **protocol/tool adapters**\n\nthen Ochat is best thought of as:\n\n**a text-first toolkit for reproducible, composable LLM workflows**\n\n---\n\n## Who Ochat is for\n\nOchat is for developers and power users who want:\n- custom AI agents instead of fixed app behavior\n- workflows they can version-control and diff\n- explicit tool and transcript state\n- reproducible runs across local, development, and CI environments\n- a local-first, text-first workflow model\n\nOchat is probably not the best fit if you just want the simplest possible chat UI with minimal setup and no interest in workflow artifacts.\n\n---\n\n## Quick start\n\nNew to OCaml?\n\nIf you do not already have an OCaml environment set up, start here:\n\n- [OCaml.org](https://ocaml.org)\n- [Install OCaml, opam, and the toolchain](https://ocaml.org/install#linux_mac_bsd)\n- [More detailed installation guide](https://ocaml.org/docs/installing-ocaml)\n\nOchat uses the standard OCaml tooling stack:\n- **opam** for package management and compiler switches\n- **dune** for builds and tests\n\nOnce your OCaml environment is installed, you can build and run Ochat as follows.\n\nBuild and run a minimal ChatMD prompt.\n\n### 1. Install dependencies and build\n\n```sh\nopam switch create .\nopam install . --deps-only\n\ndune build\n```\n\n\n\n### 2. Create a prompt file\n\nCreate `prompts/hello.md`:\n\n```xml\n\u003cconfig model=\"gpt-5.2\" reasoning_effort=\"medium\"/\u003e\n\n\u003cdeveloper\u003e\nYou are a helpful assistant.\n\u003c/developer\u003e\n\n\u003cuser\u003e\nSay hello and explain what Ochat is in one sentence.\n\u003c/user\u003e\n```\n\n### 3. Run it in the terminal UI\n\n```sh\ndune exec chat_tui -- -file prompts/hello.md\n```\n\n### 4. Or run it non-interactively\n\n```sh\nochat chat-completion \\\n  -prompt-file prompts/hello.md \\\n  -output-file .chatmd/hello-run.md\n```\n\nThe output file captures the run as a plain text artifact that you can inspect, diff, resume, or share.\n\n---\n\n## First 10 minutes with Ochat\n\nA simple way to get a feel for Ochat:\n\n1. **Set up OCaml tooling**  \n   If needed, install OCaml, opam, and the toolchain:\n   - [OCaml.org](https://ocaml.org)\n   - [Install guide](https://ocaml.org/install#linux_mac_bsd)\n   - [VSCode one click install walkthrough via OCaml Platform plugin](https://tarides.com/blog/2026-04-16-vscode-walkthrough-installing-ocaml-in-1-click/?utm_source=dlvr.it\u0026utm_medium=linkedin)\n\n2. **Build the project**\n   ```sh\n   opam switch create .\n   opam install . --deps-only\n   dune build\n   ```\n\n3. **Run a minimal prompt**\n   Create `prompts/hello.md` and run it in the TUI:\n   ```sh\n   dune exec chat_tui -- -file prompts/hello.md\n   ```\n\n4. **Try a tool-using prompt**\n   Run the refactor example:\n   ```sh\n   dune exec chat_tui -- -file prompts/refactor.md\n   ```\n\n5. **Inspect the workflow artifact**\n   Export or save the run and open the resulting `.md` / `.chatmd` file to see:\n   - the prompt\n   - the transcript\n   - tool calls and results\n   - the exact workflow state captured as text\n\n6. **Try a non-interactive run**\n   ```sh\n   ochat chat-completion \\\n     -prompt-file prompts/hello.md \\\n     -output-file .chatmd/hello-run.md\n   ```\n\n7. **Explore deeper features**\n   From there, try:\n   - agent-as-tool composition\n   - MCP export via `mcp_server`\n   - retrieval/indexing tools\n   - ChatML moderator scripts\n\n---\n\n## What can I do with Ochat?\n\n### Author workflows as plain files\nWrite agents as `.md` files using ChatMD. A file can act as both:\n- a reusable prompt definition\n- the execution log of a run\n\nThat means prompts, tool calls, results, and transcripts can all be version-controlled and diffed like code.\n\n### Build tool-using agents\nCombine:\n- ChatMD prompt instructions\n- built-in tools\n- shell wrappers\n- remote MCP tools\n- other agents mounted as tools\n\nBuilt-in tools include capabilities such as:\n- repo-safe editing via `apply_patch`\n- filesystem access via `read_dir` and `read_file`\n- web ingestion via `webpage_to_markdown`\n- retrieval over docs via `index_markdown_docs` and `markdown_search`\n- retrieval over OCaml code via `index_ocaml_code` and `query_vector_db`\n- image import via `import_image`\n\nSee [Tools – built-ins, custom helpers \u0026 MCP](docs-src/overview/tools.md).\n\n### Compose agents into prompt packs\nBuild Claude Code/Codex-style applications out of multiple prompts:\n- planning agents\n- coding agents\n- test agents\n- documentation agents\n- orchestration agents\n\nBecause prompts can be mounted as tools, you can create modular multi-agent systems without hard-coding everything into one app.\n\n### Run the same workflow in different hosts\nUse:\n- `chat_tui` for interactive work\n- `ochat chat-completion` for scripts, CI, and cron\n- `mcp_server` to expose prompts as tools to IDEs and other clients\n\n### Ground agents in your own corpus\nCreate indexes over docs or source trees and let prompts query them using natural language. See [Search, indexing \u0026 code intelligence](docs-src/guide/search-and-indexing.md).\n\n### Refine prompts iteratively\nUse the `mp-refine-run` binary to generate, evaluate, and improve prompts and tool descriptions through iterative meta-prompting.\n\n### Version, branch, and resume runs\nBecause conversation state is stored in text files, you can export full runs, branch them, resume them later, and review exactly what changed.\n\n---\n\n\n## Common use cases\n\nOchat is useful anywhere you want LLM workflows to be explicit, reproducible, and easy to evolve.\n\nTypical use cases include:\n\n- **Repo-aware coding assistants**  \n  Build agents that inspect a codebase, read files, propose patches, and run in a controlled local workflow.\n\n- **Documentation agents**  \n  Create prompts that summarize docs, update documentation, or answer questions over local documentation sets.\n\n- **Planning / review / test workflows**  \n  Compose multiple prompts into planning, implementation, review, and test stages.\n\n- **Prompt packs for internal tools**  \n  Define reusable sets of prompts and tools for domain-specific workflows without burying them inside a UI.\n\n- **Retrieval-grounded assistants**  \n  Index local docs or source trees and let prompts query them with natural language.\n\n- **CI and scripted runs**  \n  Run prompts non-interactively in scripts, CI jobs, or recurring automation tasks.\n\n- **MCP-exposed prompt tools**  \n  Publish prompts as MCP tools so IDEs and other hosts can call them over stdio or HTTP/SSE.\n\n---\n\n\n## Example ChatMD prompts\n\n### Example: minimal prompt\n\nCreate `prompts/hello.md`:\n\n```xml\n\u003cconfig model=\"gpt-5.2\" reasoning_effort=\"medium\"/\u003e\n\n\u003cdeveloper\u003e\nYou are a helpful assistant.\n\u003c/developer\u003e\n\n\u003cuser\u003e\nSay hello and explain what Ochat is in one sentence.\n\u003c/user\u003e\n```\n\nRun it:\n\n```sh\ndune exec chat_tui -- -file prompts/hello.md\n```\n\nOr:\n\n```sh\nochat chat-completion \\\n  -prompt-file prompts/hello.md \\\n  -output-file .chatmd/hello-run.md\n```\n\n---\n\n### Example: interactive refactor agent\n\nTurn a `.md` file into a refactoring bot that reads files and applies patches under your control.\n\nCreate `prompts/refactor.md`:\n\n```xml\n\u003cconfig model=\"gpt-5.2\" reasoning_effort=\"medium\"/\u003e\n\n\u003ctool name=\"read_dir\"/\u003e\n\u003ctool name=\"read_file\"/\u003e\n\u003ctool name=\"apply_patch\"/\u003e\n\n\u003cdeveloper\u003e\nYou are a careful refactoring assistant. Work in small, reversible steps.\nBefore calling apply_patch, explain the change you want to make and wait for\nconfirmation from the user.\n\u003c/developer\u003e\n\n\u003cuser\u003e\nWe are in a codebase. Look under ./lib, find a small improvement and\npropose a patch.\n\u003c/user\u003e\n```\n\nOpen it in the TUI:\n\n```sh\ndune exec chat_tui -- -file prompts/refactor.md\n```\n\nFrom there you can ask the assistant to rename a function, extract a helper, or\nupdate documentation. It will use `read_dir` and `read_file` to inspect the\ncode, then generate `apply_patch` diffs and apply them.\n\n---\n\n### Example: publish a prompt as an MCP tool\n\nExport a `.md` file as a remote tool that other MCP-compatible clients can call.\n\nCreate `prompts/hello.md`:\n\n```xml\n\u003cconfig model=\"gpt-5.2\" reasoning_effort=\"medium\"/\u003e\n\n\u003ctool name=\"read_dir\"/\u003e\n\u003ctool name=\"read_file\"/\u003e\n\n\u003cdeveloper\u003eYou are a documentation assistant.\u003c/developer\u003e\n\n\u003cuser\u003e\nList the files under docs-src/ and summarize what each top-level folder is for.\n\u003c/user\u003e\n```\n\nStart the MCP server so it exports `hello.md` as a tool:\n\n```sh\ndune exec mcp_server -- --http 8080\n```\n\nAny MCP client can now discover the `hello` tool via `tools/list` and call it\nwith `tools/call` over JSON-RPC. For example:\n\n```sh\ncurl -s http://localhost:8080/mcp \\\n  -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/list\",\"params\":{}}'\n```\n\nThe response includes an entry for `hello` whose JSON schema is inferred from\nthe ChatMD file.\n\n---\n\n### Example: moderated ChatMD prompt\n\nYou can attach one ChatML moderator script to a prompt and keep it host-managed.\n\nCreate `prompts/review.chatmd`:\n\n```md\n\u003cconfig model=\"gpt-5.2\" reasoning_effort=\"medium\"/\u003e\n\n\u003ctool name=\"read_file\"/\u003e\n\u003ctool name=\"apply_patch\"/\u003e\n\n\u003cscript language=\"chatml\" kind=\"moderator\" id=\"main\"\u003e\n\ntype state =\n  { reminded : bool }\n\ntype event =\n  [ `Session_start\n  | `Session_resume\n  | `Turn_start\n  | `Item_appended(item)\n  | `Pre_tool_call(tool_call)\n  | `Post_tool_response(tool_result)\n  | `Turn_end\n  ]\n\nlet initial_state : state =\n  { reminded = false }\n\nlet on_event : context -\u003e state -\u003e event -\u003e state task =\n  fun ctx st ev -\u003e\n    match ev with\n    | `Session_start -\u003e\n      let* () =\n        Turn.prepend_system(\n          \"Before calling apply_patch, explain the change briefly.\"\n        )\n      in\n      Task.pure(st)\n    | _ -\u003e\n      Task.pure(st)\n\n\u003c/script\u003e\n\n\u003cdeveloper\u003e\nYou are a careful code assistant.\n\u003c/developer\u003e\n\n\u003cuser\u003e\nReview lib/example.ml and suggest a small safe improvement.\n\u003c/user\u003e\n```\n\nThis example prepends a system instruction at session start, requiring the assistant to explain changes briefly before using `apply_patch`.\n\n\nRun it in the TUI or CLI:\n\n```sh\ndune exec chat_tui -- -file prompts/review.chatmd\n```\n\n```sh\nochat chat-completion \\\n  -prompt-file prompts/review.chatmd \\\n  -output-file .chatmd/review-run.chatmd\n```\n\nFor a richer end-to-end example, see:\n- [General Assistant – agent workflow](docs-src/guide/general-agent-workflow.md)\n- [prompt-examples](prompt-examples/readme.md)\n\n### Writing moderator scripts with `Item.*`\n\nBy default, moderator scripts get the `Item`, `Tool_call`, and `Context`\nhelper modules on the installed moderator surface, so common transcript,\ntool-call, and context queries do not need raw record or JSON plumbing.\n\nModerator scripts receive `ctx.items`, where each item has the shape:\n\n```ocaml\ntype item =\n  { id : string\n  ; value : json\n  }\n```\n\nThe `Item` module provides helpers so scripts do not need to hand-author raw\nJSON for common cases:\n\n- `Item.id(item)` returns the stable item id used by overlay operations\n- `Item.value(item)` returns the underlying structured JSON payload\n- `Item.kind(item)` reads the serialized item `\"type\"` field when present\n- `Item.role(item)` extracts a message role when the item has one\n- `Item.text_parts(item)` collects text fragments from common message-like items\n- `Item.text(item)` returns the first text fragment when one is present\n- `Item.input_text_message(id, role, text)` builds a structured input message\n- `Item.output_text_message(id, text)` builds a structured assistant message\n- `Item.user_text(id, text)`, `Item.assistant_text(id, text)`,\n  `Item.system_text(id, text)`, and `Item.notice(id, text)` are convenience\n  constructors over those message shapes\n- `Item.is_user(item)`, `Item.is_assistant(item)`, `Item.is_system(item)`,\n  `Item.is_tool_call(item)`, and `Item.is_tool_result(item)` are predicate\n  helpers over the serialized role/kind fields\n- `Item.create(id, value)` wraps arbitrary structured JSON as an item\n\nThe default moderator surface also exposes pure inspector helpers for tool\ncalls and the current moderation context:\n\n- `Tool_call.arg(call, name)` returns the raw JSON argument when it is present\n- `Tool_call.arg_string(call, name)`, `Tool_call.arg_bool(call, name)`, and\n  `Tool_call.arg_array(call, name)` return `Option.none()` when the argument is\n  missing or has the wrong JSON shape\n- `Tool_call.is_named(call, name)` and `Tool_call.is_one_of(call, names)` match\n  against the serialized tool name using exact string equality\n- `Context.last_item(ctx)`, `Context.last_user_item(ctx)`,\n  `Context.last_assistant_item(ctx)`, `Context.last_system_item(ctx)`,\n  `Context.last_tool_call(ctx)`, and `Context.last_tool_result(ctx)` return the\n  last matching item in `ctx.items`\n- `Context.find_item(ctx, id)` uses exact item-id equality\n- `Context.items_since_last_user_turn(ctx)` and\n  `Context.items_since_last_assistant_turn(ctx)` return the suffix beginning at\n  the matching boundary item, or the full item list when no such boundary\n  exists\n- `Context.items_by_role(ctx, role)` uses exact role-string equality\n- `Context.find_tool(ctx, name)` and `Context.has_tool(ctx, name)` inspect\n  `ctx.available_tools` using exact tool-name equality\n\nExample:\n\n```ocaml\nlet first_text : string array -\u003e string =\n  fun parts -\u003e\n    if Array.length(parts) == 0 then \"\" else Array.get(parts, 0)\n\nlet on_event : context -\u003e state -\u003e event -\u003e state task =\n  fun ctx st ev -\u003e\n    match ev with\n    | `Item_appended(item) -\u003e\n      let summary =\n        Item.id(item)\n        ++ \":\"\n        ++ Option.get_or(Item.role(item), \"unknown\")\n        ++ \":\"\n        ++ first_text(Item.text_parts(item))\n      in\n      Task.bind(Turn.append_item(Item.output_text_message(\"summary\", summary)), fun ignored -\u003e\n      Task.pure(st))\n    | _ -\u003e\n      Task.pure(st)\n```\n\nPrefer `Turn.append_item`, `Turn.replace_item`, and `Turn.delete_item`.\nThe older `append_message`, `replace_message`, and `delete_message` names are\nstill accepted as aliases.\n\nAdditional Phase 1 script helpers:\n\n- `Turn.replace_or_append(target_id_opt, item)` replaces when\n  `target_id_opt` is `Option.some(id)` and appends when it is\n  `Option.none()`\n- `Turn.append_notice(text)` appends a synthetic system notice item using a\n  stable `system:`-prefixed id derived from the notice text\n- `Model.call_text(recipe, text)` is shorthand for\n  `Model.call(recipe, `String(text))`\n- `Model.call_json(recipe, payload)` is a named alias for\n  `Model.call(recipe, payload)` when the payload is already structured JSON\n- `Model.spawn_text(recipe, text)` is shorthand for\n  `Model.spawn(recipe, `String(text))`\n\n### Runtime semantics in `chat_tui`\n\nWhen a prompt runs in `chat_tui`, moderation is split across three layers:\n\n- `Moderator_manager` owns durable moderator state, overlay state, and queued\n  internal events.\n- `In_memory_stream` owns one active model/tool turn.\n- `chat_tui` owns the session controller that drains wakeups while idle,\n  refreshes visible transcript state, and schedules follow-up turns without\n  creating fake user messages.\n\nThe visible transcript in the UI is a projection of canonical history through\nthe moderator overlay. During streaming, `chat_tui` still applies token and\ntool patches directly for responsiveness, then reprojects the visible\ntranscript at safe points such as:\n\n- idle/background moderator drains,\n- the end of a streamed turn,\n- the end of compaction,\n- startup or resume moderation.\n\nFor the concrete host/session-controller contract behind those behaviors, see\n[ChatML host session-controller contract](docs-src/chatml-host-session-controller-contract.md).\n\nFor the canonical safe-point and effective-history semantics behind request\npreparation, overlay projection, deferred steering, restore, and visible\nhistory refresh, see\n[ChatML safe-point and effective-history semantics](docs-src/chatml-safe-point-and-effective-history.md).\n\nFor a consolidated overview of the moderator runtime layers, builtin surfaces,\nevent types, helper modules, runtime requests, internal events, and UI-only\ncapabilities, see\n[ChatML moderator runtime guide](docs-src/guide/chatml-moderator-runtime.md).\n\nFor the concrete budget contract that bounds self-triggered turns, idle\ninternal-event drains, automatic follow-up scheduling, and the ownership of\n`max_spawned_jobs`, see\n[ChatML budget policy](docs-src/chatml-budget-policy.md).\n\nFor the end-to-end async completion and wakeup path behind `Model.spawn`,\nqueued moderator internal events, idle drains, and the current `Job` deferral,\nsee\n[ChatML async completion lifecycle](docs-src/chatml-async-completion-lifecycle.md).\n\nFor the optional UI-only notification and ask-user capability layer used by\ninteractive hosts such as `chat_tui`, see\n[ChatML UI host capabilities](docs-src/chatml-ui-host-capabilities.md).\n\nThat UI layer adds `Ui.notify`, `Approval.ask_text`, and\n`Approval.ask_choice` only on the dedicated UI surface.\n`Ui.notify` is a host-local notice path: it does not mutate canonical history\nand does not append transcript items automatically. The `Approval.ask_*` flow\nis live-session-only: it pauses the current script, exposes one pending UI\nrequest to the host, and resumes the same script execution from that call site\nwhen the UI submits a validated response. It is not the same as deferred\nsafe-point steering input, and pending approvals are not persisted across\nrestart or snapshot restore.\n\nTwo host behaviors are especially important:\n\n1. **Idle async wakeups**\n\n   Background producers such as `Model.spawn` completions may enqueue moderator\n   internal events while no turn is active. The host wakes the session\n   controller, drains those internal events, refreshes visible transcript\n   state, and may schedule an idle follow-up turn if the moderator requests\n   one.\n\n2. **Deferred steering notes**\n\n   If the user submits steering text while a turn is already streaming, the\n   host does not splice a new canonical user message into the in-flight model\n   request. Instead it stores a deferred steering note and injects it only at\n   the next safe model-input boundary. This preserves the current reasoning and\n   tool workflow while still letting the user steer the next request.\n\nAn end-to-end idle async completion looks like this:\n\n1. a moderator script previously calls `Model.spawn(...)`\n2. the spawned job finishes and is reinjected as an internal event\n3. the idle `chat_tui` session receives a moderator wakeup\n4. the reducer drains queued internal events through `Moderator_manager`\n5. any overlay changes are reprojected into the visible transcript\n6. if the moderator emitted `Runtime.request_turn()`, the host starts one more\n   ordinary turn from the current session state\n\nAn end-to-end deferred-steering flow during a tool run looks like this:\n\n1. the assistant is in the middle of a streamed turn or tool workflow\n2. the user submits steering text\n3. the host records a deferred steering note instead of appending a canonical\n   user item mid-turn\n4. the current turn reaches a safe point and eventually completes\n5. the next request is prepared from moderator-effective history\n6. the deferred steering note is appended as transient system input for that\n   request only\n\n---\n\n## Build from source (OCaml)\n\nInstall dependencies, build, and run tests:\n\n```sh\nopam switch create .\nopam install . --deps-only\n\ndune build\ndune runtest\n```\n\n\u003e On Apple Silicon (macOS arm64), Owl's OpenBLAS dependency can sometimes fail\n\u003e to build during `opam install`. If you see BLAS/OpenBLAS errors while\n\u003e installing dependencies or running `dune build`, see\n\u003e [Build \u0026 installation troubleshooting](docs-src/guide/build-troubleshooting.md#owl--openblas-on-apple-silicon-macos-arm64)\n\u003e for a proven workaround.\n\nRun an interactive session with the terminal UI:\n\n```sh\ndune exec chat_tui -- -file prompts/interactive.md\n```\n\nOr run a non-interactive chat completion over a ChatMD prompt as a smoke test:\n\n```sh\nochat chat-completion \\\n  -prompt-file prompts/hello.md \\\n  -output-file .chatmd/smoke.md\n```\n\nFor more on `ochat chat-completion` (flags, exit codes, ephemeral runs), see\n[`docs-src/cli/chat-completion.md`](docs-src/cli/chat-completion.md).\n\n---\n\n## Core concepts\n\n- **ChatMarkdown (ChatMD)**  \n  A Markdown + XML dialect that stores model config, tool declarations, and the full conversation (including tool calls, reasoning traces, and imported artifacts) in a single `.md` file. See the [language reference](docs-src/overview/chatmd-language.md).\n\n- **Tools**  \n  Functions the model can call, described by explicit JSON schemas. They can be built-ins, shell wrappers, other ChatMD agents, or remote MCP tools. See [Tools – built-ins, custom helpers \u0026 MCP](docs-src/overview/tools.md).\n\n- **chat_tui**  \n  A Notty-based terminal UI for editing and running `.md` files. It turns each prompt into a terminal application with streaming output, persistent sessions, and export/branch workflows. See the [chat_tui guide](docs-src/guide/chat_tui.md).\n\n- **CLI and helpers**  \n  Binaries like `ochat`, `md-index`, and `md-search` provide script-friendly entry points for running prompts and building/querying indexes.\n\n- **MCP server**  \n  `mcp_server` turns `.md` files and selected tools into MCP resources and tools that other applications can list and call over stdio or HTTP/SSE. See the [mcp_server binary doc](docs-src/bin/mcp_server.doc.md).\n\n- **Search \u0026 indexing**  \n  Modules and binaries that build vector indexes over markdown docs and source code, powering tools like `markdown_search` and `query_vector_db`. See [Search, indexing \u0026 code intelligence](docs-src/guide/search-and-indexing.md).\n\n- **Meta-prompting**  \n  A library and CLI (`mp-refine-run`) for generating, scoring, and refining prompts in a loop. See the [`Meta_prompting` overview](docs-src/lib/meta_prompting.doc.md).\n\n---\n\n## Architecture overview\n\nAt a glance, Ochat treats workflows as text artifacts executed by a host runtime.\n\n```text\n                 ChatMD workflow file\n   ┌──────────────────────────────────────────────┐\n   │ config                                       │\n   │ tools                                        │\n   │ prompt messages                              │\n   │ transcript / tool calls / tool results       │\n   │ optional ChatML moderation script            │\n   └──────────────────────────────────────────────┘\n                           │\n                           ▼\n          ┌──────────────────────────────────┐\n          │ Host runtime                     │\n          │ - `chat_tui`                     │\n          │ - `ochat chat-completion`        │\n          │ - `mcp_server`                   │\n          └──────────────────────────────────┘\n              │                  │\n              ▼                  ▼\n      ┌───────────────┐   ┌──────────────────┐\n      │ Model backend │   │ Tool execution   │\n      │ OpenAI today  │   │ built-ins/shell/ │\n      │ more later    │   │ MCP/other agents │\n      │ or use proxy  │   └──────────────────┘ \n      └───────────────┘          │\n              │                  │\n              └──────────┬───────┘\n                         ▼\n             Updated ChatMD transcript/state\n           (diffable, reproducible, resumable)\n```\n\nAt a high level, Ochat has two layers:\n\n- **ChatMD** is the workflow document format  \n  It stores prompts, tools, transcript state, and execution artifacts.\n\n- **ChatML** is the optional host-managed scripting layer  \n  It adds workflow logic such as moderation, transcript editing, policy enforcement, and multi-step orchestration.\n\nA typical flow looks like this:\n\n1. Load a ChatMD file\n2. Parse config, tools, transcript, and optional script\n3. Run it in a host (`chat_tui`, CLI, or MCP)\n4. Let the model call tools and produce output\n5. Optionally let a ChatML moderator inspect/modify the effective transcript or tool flow\n6. Persist the resulting workflow state back as text artifacts\n\nThis split keeps workflows inspectable while still allowing advanced control logic.\n\nFor moderated interactive sessions, the host runtime has a more specific role\nsplit:\n\n- `Moderator_manager` keeps durable moderator state, overlay state, halted\n  state, and queued internal events.\n- `In_memory_stream` executes one active completion/tool-followup loop at a\n  time and handles explicit safe points such as turn start, post-tool-result,\n  and turn end.\n- `chat_tui` runs a single-threaded session controller over its reducer loop.\n  That controller reacts to moderator wakeups while idle, defers wakeups while\n  a turn is active, refreshes visible transcript state from moderator-effective\n  history at safe points, and starts follow-up turns only when the UI is idle.\n\nThe concrete Phase 2 articulation of that host layer lives in\n[docs-src/chatml-host-session-controller-contract.md](docs-src/chatml-host-session-controller-contract.md).\n\n\n---\n\n## Documentation\n\nDeep-dive docs live under `docs-src/`. Key entry points:\n\n- [ChatMarkdown language reference](docs-src/overview/chatmd-language.md)\n- [Built-in tools \u0026 custom tools](docs-src/overview/tools.md)\n- [chat_tui guide \u0026 key bindings](docs-src/guide/chat_tui.md)\n- [`ochat chat-completion` CLI](docs-src/cli/chat-completion.md)\n- [MCP server \u0026 protocol details](docs-src/bin/mcp_server.doc.md)\n- [Search, indexing \u0026 code intelligence](docs-src/guide/search-and-indexing.md)\n- [Meta-prompting \u0026 Prompt Factory](docs-src/lib/meta_prompting.doc.md)\n- [Real-world example session: updating the tools docs](real-world-example-session/update-tool-docs/readme.md)\n- [ChatML moderator runtime guide](docs-src/guide/chatml-moderator-runtime.md)\n- [ChatML host session-controller contract](docs-src/chatml-host-session-controller-contract.md)\n- [ChatML safe-point and effective-history semantics](docs-src/chatml-safe-point-and-effective-history.md)\n- [ChatML budget policy](docs-src/chatml-budget-policy.md)\n- [ChatML async completion lifecycle](docs-src/chatml-async-completion-lifecycle.md)\n- [ChatML UI host capabilities](docs-src/chatml-ui-host-capabilities.md)\n\n---\n\n## Binaries\n\n| Binary | Purpose | Example |\n|--------|---------|---------|\n| `chat_tui` (`chat-tui`) | interactive TUI | `chat_tui -file notes.md` |\n| `ochat` | misc CLI (index, query, tokenise …) | `ochat query -vector-db-folder _index -query-text \"tail-rec map\"` |\n| `mcp_server` | serve prompts \u0026 tools over JSON-RPC / SSE | `mcp_server --http 8080` |\n| `mp-refine-run` | refine prompts via recursive meta-prompting | `mp-refine-run -task-file task.md -input-file draft.md` |\n| `md-index` / `md-search` | Markdown → index / search | `md-index --root docs`; `md-search --query \"streams\"` |\n| `odoc-index` / `odoc-search` | (OCaml) odoc HTML → index / search | `odoc-index --root _doc/_html` |\n\nRun any binary with `-help` for details.\n\n---\n\n## Project layout\n\n```text\nbin/         – chat_tui, mcp_server, ochat …\nlib/         – re-usable libraries (chatmd, functions, vector_db …)\ndocs-src/    – Markdown docs rendered by odoc \u0026 included here\nprompts/     – sample ChatMD prompts served by the MCP server\ndune-project – dune metadata\n```\n\n---\n\n## OCaml integration\n\nOchat is implemented in OCaml. While the workflows themselves are language-agnostic, Ochat has first-class support for OCaml development workflows.\n\n### Why OCaml?\n\nOchat is heavy on:\n- structured data and parsing\n- symbolic transformations\n- workflow state modeling\n- reliability-sensitive orchestration\n- compiler- and test-driven repair loops\n\nThese are all areas where OCaml is especially strong.\n\n### OCaml-specific entry points\n\n- **OCaml development environment guide**  \n  See [`DEVELOPMENT.md`](DEVELOPMENT.md) for a walkthrough that sets up local OCaml documentation, search indexes, and related workflows.\n\n- **OCaml API doc search**  \n  `odoc-index` / `odoc-search` index and search generated odoc HTML.\n\n- **Embedding as a library**  \n  Use the OCaml libraries directly. See [Embedding Ochat in OCaml](docs-src/lib/embedding.md).\n\n- **OCaml indexing \u0026 code intelligence**  \n  Ochat can parse and index OCaml source directly (no LSP dependency) to build precise code search and code-aware agent workflows.\n\n### Using builds/tests as an LLM feedback loop\n\nWhen you run Ochat against an OCaml repository, the usual `dune build` / `dune runtest` loop becomes a high-signal feedback channel for LLM-generated edits: let an agent propose `apply_patch` diffs, run the build and tests, then feed compiler errors or failing tests back into the next turn.\n\n### ChatML (experimental, but integrated for moderation)\n\nThe repository ships an experimental language called **ChatML**: a small, expression-oriented ML dialect with Hindley–Milner type inference extended with row polymorphism for records and variants.\n\nToday ChatML is integrated primarily as the host-managed moderation layer for ChatMD:\n\n- a prompt may declare one `\u003cscript language=\"chatml\" kind=\"moderator\" ...\u003e`\n- the script runs through the shared moderation manager used by `chat_tui`, file-backed drivers, nested agents, and MCP prompt wrappers\n- moderator scripts receive `ctx.items` where each item is `{ id; value : json }`\n- moderator scripts can modify transcript items and moderate tool calls\n- moderator scripts can request another turn via `Runtime.request_turn()`\n- moderator scripts can call host-registered model recipes via `Model.call`\n- moderator scripts can spawn background model jobs via `Model.spawn`\n\nFor the concrete async reinjection, wakeup, and safe-boundary lifecycle behind\n`Model.spawn`, see\n[ChatML async completion lifecycle](docs-src/chatml-async-completion-lifecycle.md).\n\nOutside that moderation integration, ChatML is also available through the experimental `dsl_script` binary and the `Chatml_*` library modules.\n\nPhase 1 also exposes clearer OCaml entrypoints for embedders:\n\n- `Chatml_runtime` is the public runtime wrapper over\n  `Chatml_moderator_runtime`\n- `Chat_response.Chatml_moderation` is the structured moderation vocabulary\n  wrapper over `Moderation`\n- `Chat_response.Chatml_moderator` is the durable effective-history and\n  snapshot boundary over `Moderator_manager`\n- `Chat_response.Chatml_turn_driver` is the public safe-point and turn-input\n  helper layer over `In_memory_stream`\n\nThe older module names still exist as implementation anchors inside the\nlibraries, but new embedder-facing code should prefer the wrapper modules\nabove.\n\nFor more details, see:\n- [`docs-src/guide/chatml-language-spec.md`](docs-src/guide/chatml-language-spec.md)\n- [`docs-src/guide/chatml-match-semantics.md`](docs-src/guide/chatml-match-semantics.md)\n- [`docs-src/lib/chatml/chatml_lang.doc.md`](docs-src/lib/chatml/chatml_lang.doc.md)\n- [`docs-src/lib/chatml/chatml_parser.doc.md`](docs-src/lib/chatml/chatml_parser.doc.md)\n- [`docs-src/lib/chatml/chatml_resolver.doc.md`](docs-src/lib/chatml/chatml_resolver.doc.md)\n\n---\n\n## Future directions\n\nOchat is intentionally **agent-first**: the roadmap focuses on making ChatMD, the runtime, and `chat_tui` more expressive for building and operating fleets of custom agents.\n\nPlanned and experimental directions include:\n\n- **Explicit control-flow \u0026 policy in ChatMD**  \n  A design sketch for a rules layer over ChatMD that could express things like auto-compaction, tool validation policies, and declarative control flow without hiding logic from the transcript.\n\n- **Richer session tracking, branching, and evaluation**  \n  Better first-class support for branching conversations, long-term archives, and evaluation runs.\n\n- **Session data backed by Irmin**  \n  A planned per-session state and filesystem model with isolation, persistence, and versioning.\n\n- **Additional LLM providers**  \n  Today Ochat integrates with OpenAI; future work is intended to support additional backends while keeping ChatMD and tool contracts stable.\n  You can use a proxy server that maps the Openai api Response endpoint format to your preferred providers format. Example: [LiteLLm](https://docs.litellm.ai/) and set the enviorment url API_URL to the proxy url. \n\n- **Broader ChatML scripting roles**  \n  ChatML is currently focused on moderation and orchestration; the longer-term plan is to broaden that scripting role without sacrificing safety or auditability.\n\n- **Custom OCaml functions as tools via Dune plugins**  \n  A planned direction is to expose custom OCaml functions as tools via Dune plugins.\n\nAll of these directions share the same goal: make agents more reliable, composable, and expressive **without** sacrificing the “everything is a text file” property.\n\n---\n\n## Project status – expect rapid change\n\nOchat is a **fast-moving project**.\n\nThe core file-oriented workflow model is designed to stay stable, but APIs, tool schemas, and some higher-level design choices may continue to evolve as the project explores what works best.\n\nDespite the experimental label, **you can build real value today**. The repository already enables powerful custom agent workflows for development, documentation, writing, and automation.\n\nPlease budget time for occasional refactors and breaking changes.\n\nBug reports, feature requests, and PRs are very welcome.\n\nDocumentation is also a work in progress. If you find gaps or rough edges, please open issues or PRs to help improve it.\n\n---\n\n## License\n\nAll original source code is licensed under the terms stated in `LICENSE.txt`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdakotamurphyucf%2Fochat","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdakotamurphyucf%2Fochat","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdakotamurphyucf%2Fochat/lists"}