LLM agent built to run anywhere
TUI, ACP agent, server — or embed as Rust library.

or if you prefer GUI you can check openheim app
One agent.
Many ways
to run it.
From your editor to production APIs.
Or just open a terminal.
Run openheim with no arguments and you get a full chat UI. The footer shows the live context size, and tool calls wait for your approval.

:modelsSwitch model mid-session and keep the conversation going:sessionsBrowse saved conversations and pick up any of them:mcpSee which MCP servers are connected:skillsList the skills you can load
Works inside your editor.
openheim implements the Agent Client Protocol (ACP) — run it as a subprocess inside your editor or deploy it as a remote WebSocket server. The same agent logic powers both.
openheim acpNative editor agent
Runs as a subprocess — no port, no daemon. Register it in your editor's ACP agent settings and you have a full tool-equipped agent inside Zed, JetBrains, or any ACP-compatible client. Skills, MCP servers, and conversation history all work identically.
# ACP stdio — register in your editor's agent settings
openheim acp
# Compatible with Zed, JetBrains,
# and any ACP-compliant editor extension.openheim serveSelf-hosted server
Exposes ACP over a multiplexed WebSocket and a REST API. Multiple clients connect simultaneously — each session gets isolated context. Conversation history survives WebSocket reconnections and server restarts.
# Start the WebSocket + REST server
openheim serve
# ws://localhost:1217/ws (ACP agent + filesystem)
# http://localhost:1217/api/* (config, models, sessions)Run openheim in any environment.
Home, cloud, or enterprise — choose the right operating mode for your team.
Home / Local
- —Interactive TUI for conversational workflows — :sessions to restore any past conversation
- —openheim run "<prompt>" for quick one-off headless tasks
- —openheim acp to integrate directly with Zed, JetBrains, or any ACP editor
Cloud / API
- —openheim serve exposes ACP over WebSocket and a REST API on port 1217
- —Multiple ACP clients connect simultaneously — each session is fully isolated
- —Conversation history survives WebSocket reconnections and server restarts
Enterprise
- —Provider-agnostic — route to any OpenAI-compatible endpoint, Azure OpenAI, or Bedrock
- —Filesystem access scoped to workspace root; add auth at the nginx or Caddy proxy layer
- —Run one instance per tenant — separate config, history directory, and API keys
What the agent can do
execute_commandopt-inRun shell commands: scripts, build tools, git, anything the shell can do
read_fileRead the full contents of a file by path
write_fileWrite or overwrite a file at the given path, creating it if it does not exist
edit_fileReplace an exact string in a file without rewriting the whole thing
list_dirList the immediate contents of a directory, non-recursively
searchRipgrep-style regex search across files, respecting .gitignore
web_fetchFetch a public web page or text resource and return it as plain text
delegate_taskHand off a self-contained task to a subagent with its own persona, model, and tools
rememberSave a note to long-term memory so later sessions can find it
search_memorySearch saved notes by keyword, or by meaning when an embedding provider is set
edit_memoryUpdate a saved note in place
forgetDelete a saved note by id
Extend with MCP — add any MCP server to ~/.openheim/config.toml and its tools are auto-discovered at startup, namespaced as server__tool.
Safe by default.
An agent that runs tools needs limits. These are on before you change a single setting.
Read the docs on sandboxingShell is opt-in
execute_command stays hidden from the model until you set allow_shell = true in config.toml.
Files stay in work_dir
File tools can't read or write outside the work directory, and symlinks can't be used to escape it.
You approve tool calls
The TUI and ACP editors ask before each call: allow once, allow always, or reject. "Always" on a shell command covers only those exact arguments.
Read-only architect mode
Switch a session to architect mode and the agent only gets tools that can't change anything.
Guarded web fetch
web_fetch refuses private, loopback and cloud metadata addresses, and stops after 20 seconds or 256 KiB.
Bounded commands
Each shell command runs in its own process group, is killed after 120 seconds, and has its output capped at 64 KiB.
Shell commands can still use absolute paths outside work_dir. For full isolation, run openheim in a container.
Ship it your way.
Local binary
cargo install openheim or build from source. Run the TUI, openheim run for one-shot prompts, openheim acp for editor integration, or openheim serve for the WebSocket + REST server.
cargo install openheimDocker
Multi-stage Dockerfile keeps the image lean. The Compose file passes API keys as environment variables and mounts a volume for ~/.openheim, so config and conversation history survive restarts.
docker compose upCloud / self-hosted
Deploy the binary or Docker image anywhere that runs Linux — Fly.io, Railway, EC2, a Raspberry Pi. Serve on port 1217 behind your proxy of choice, managed with systemd.
any linux hostRust library
Embed the agent runtime directly in your app via the openheim crate. A single OpenheimClient facade covers sessions, streaming, skills, and MCP.
cargo add openheimExtend it in Rust.
Embed the agent in your own app, or plug in tools, model backends and approval flows. Each one is a single trait.
OpenheimClientEmbed the runtime
Sessions, streaming, history, skills, memory and MCP behind one client. Leave out the CLI, TUI and server with default-features = false.
let client = OpenheimClient::builder().build().await?;
let session = client.new_session().cwd("/my/project").start().await?;
session.prompt("Summarise the README", |event| {
if let StreamEvent::LlmResponse { content } = event {
print!("{content}");
}
}).await?; Read the docs on library usage ToolHandlerAdd your own tools
Implement two methods and the agent can call your code. Tools get the same work_dir boundary and approval flow as the built-ins.
#[async_trait]
impl ToolHandler for TicketLookup {
fn definition(&self) -> Tool { /* name, description, JSON schema */ }
async fn execute(&self, args: &str, turn: &TurnContext<'_>) -> Result<String> {
// run your code; the string goes back to the model
}
}
OpenheimClient::builder().tool(Box::new(TicketLookup)).build().await?; Read the docs on custom tools LlmClientBring any model
Wire in a backend that isn't built in: a private API, a research endpoint, or a mock for tests. OpenAI-compatible servers need no code at all.
#[async_trait]
impl LlmClient for MyProvider {
async fn send(&self, messages: &[Message], tools: &[Tool]) -> Result<Choice> {
// call your model and map its reply to a Choice:
// text, or tool calls for the agent to run
}
} Read the docs on custom LLM providers PermissionGateOwn the approval flow
Decide how tool calls get approved: a CLI prompt, a Slack message, a policy check. Subagent calls go through the same gate.
#[async_trait]
impl PermissionGate for SlackApproval {
async fn check(&self, req: &PermissionRequest<'_>) -> PermissionDecision {
// ask someone about req.tool_name and req.arguments
PermissionDecision::AllowOnce
}
}
let session = client.new_session().start().await?
.permission_gate(Arc::new(SlackApproval)); Read the docs on permission gates Common questions
How do I install openheim? +
Run cargo install openheim — it's published on crates.io. Docker images and prebuilt binaries work too. See docs.rs/openheim for full API documentation.
Does openheim require a specific LLM provider? +
No. openheim works with OpenAI, Anthropic, Gemini, and any OpenAI-compatible endpoint — including local models via Ollama, vLLM, or LM Studio. Switch providers with one line in config.toml.
Does openheim work inside my editor? +
Yes. Run openheim acp to start an ACP (Agent Client Protocol) stdio agent. Register it in your editor's ACP agent settings and you get a full tool-equipped agent inside Zed, JetBrains, and any other ACP-compatible client — no separate service needed.
Is it safe to let openheim run shell commands? +
The shell tool is off until you set allow_shell = true, and file tools can't reach outside the configured work_dir. In the TUI and in ACP editors, openheim asks before each tool call. Shell commands can still use absolute paths, so for full isolation run openheim in a container.
Can I connect external MCP servers? +
Yes. Add any MCP server to [mcp_servers] in ~/.openheim/config.toml using either stdio or Streamable HTTP transport. Tools are discovered at startup and namespaced as server__tool — available to the agent automatically.
Can I resume a conversation later? +
Yes. Every session is persisted to disk with a UUID. Resume from the interactive TUI with :sessions, via headless CLI, or over ACP with session/load — the agent continues exactly where it left off.
Does openheim remember things between sessions? +
Yes, when you ask it to. The agent saves notes with the remember tool and finds them in later sessions with search_memory. Out of the box that's keyword search in a local SQLite file, with no network calls. Add an embedding provider under [memory] and search becomes semantic. Nothing is stored unless the agent calls a memory tool.
Can openheim split work across multiple agents? +
Yes. The delegate_task tool hands a self-contained task to a subagent with its own persona, model and tools, and only the final answer comes back. Save reusable profiles as Markdown files in ~/.openheim/agents/, or let the agent define one-off subagents inline.
Can I add custom prompt context or domain expertise? +
Yes. Drop Markdown files into ~/.openheim/skills/ and reference them by name with --skills. They are injected into the system prompt at runtime — no code changes needed. ACP clients can also pass skills per-session.
Can I use openheim as a library instead of a CLI? +
Yes. Add openheim as a dependency with default-features = false to embed it directly in your own Rust application, without pulling in the CLI, TUI, or server extras.