matbot 发布极简可组合 Agent 运行时,支持插件热加载与浏览器端运行
MatAtBread/matbot
matbot 是一个连接语言模型与工具、前端的轻量可组合运行时,核心只保留智能体循环、插件管理和 principal 载体,工具、存储、前端、记忆等全部以插件形式提供并支持运行时热加载。
A thin, composable runtime that connects language models to tools and frontends.
Most LLM frameworks make choices for you. matbot doesn't. It's infrastructure: a minimal agentic loop, a plugin system, and the conviction that everything else — tools, storage, frontends, memory, identity — should be composable, hot-loadable, and replaceable without touching the core.
Try it live in your browser → (No install. Just bring an API key.)
Why matbot?
It's a TypeScript-first node v24 service, with no build-step, no config setup. All you need is an LLM (OpenAI, Anthropic, DeepSeek, Google Gemini, your own local LLM) with an API key.
It also runs entirely in the browser. matbot ships as a single self-contained HTML file — no server, no build step, no backend. The full runtime runs client-side, calling your LLM directly. Same core, same plugins, same UI as the Node build. This isn't a demo mode: it's a deliberate design choice that makes the browser a first-class, sandboxed deployment target.
Everything is a plugin. Tools, frontends, storage backends, LLM providers, knowledge indexes, session editors — all plugins. None of them are hardcoded. The core is genuinely minimal: an agentic loop, plugin management, and a principal carrier. Everything else is optional. A basic set of plugins for file access is bundled so you can get started immediately, but every one can be replaced with your preferred setup.
Plugins are hot-loaded. Add, remove, or reload a plugin at runtime without
restarting. The built-in plugin tool lets the LLM manage its own capabilities
mid-session. You can go from a bare conversational bot to one with bash execution, a web
UI, memory, skills and a Telegram frontend without ever touching a config file.
Security is a first-class concern. The principal carrier threads identity through every layer — tools, storage, knowledge index — without it needing to be in every signature. Credentials are resolved at runtime and never written to session storage. In the browser, the sandbox means even an overenthusiastic LLM can't touch your filesystem.
Quick start (Node)
matbot is a small CLI plus a set of optional plugins you install alongside it — so install
it into a project, not globally. That way the CLI and its plugins share one node_modules
and resolve to a single core (a -g or bare npx install runs fine but can't load
project-local plugins, which are the whole point):
mkdir my-matbot && cd my-matbot npm init -y npm i @matatbread/matbot-cli # creates a minimal package.json + node_modules npx matbot # runs the locally-installed `matbot` bin
Just kicking the tyres, no plugins?
npx @matatbread/matbot-cliruns it standalone.
From source, which includes all the optional plugins and the web-bundler, or for development:
git clone https://github.com/MatAtBread/matbot.git # ...or fork your own pnpm install pnpm repl
pnpm replis the from-source alias formatbot.
No config file needed — on first run matbot walks you through setting up a provider. After
that, capabilities are added with the built-in plugin tool; each is its own npm package
(see What's in the box for the catalogue):
you: I'd like a browser UI — add the web frontend
⚙ plugin { "action": "add", "specifier": "@matatbread/matbot-frontend-web" }
Install plugin "@matatbread/matbot-frontend-web"? [y/N] y
Installing with npm…
[frontend-web] http://localhost:19778
The web frontend is live — open http://localhost:19778 in your browser.
Your current session continues there; no restart needed.
That last bit is the point: the plugin hot-loads, prints a URL, and you move seamlessly
from the terminal into the web UI — same session, no interruption. (plugin add installs the
package into your project alongside the CLI, so it resolves to the same core.)
No API key? The
customer-servicesprovider is free, needs no key, and runs without GPU support. It's not a real LLM, but it's useful for testing your setup
Quick start (Browser)
Open matbot.html in any modern browser. On first launch it asks for a provider (endpoint, model, API key — DeepSeek, Anthropic, OpenAI, or anything compatible). Then you're chatting. Sessions, skills, workspace files, and provider config all persist in browser storage across reloads.
What's in the box
Core
- Agentic loop with tool-call / tool-result handling
- Plugin lifecycle: add, remove, reload, discover local plugins
- Provider management: add and switch LLM profiles live
- Principal carrier: ambient identity threaded through every layer
- Vault: secret resolution with
${NAME}placeholders - Basic implementations for storage (files) and UI (CLI) are created by "apps" like the CLI.
Plugins
All the plugins are optional - install and try out as you please. You can use them as is, or as templates for your own, bespoke implementations. Note that some, like providers are loaded indirectly when needed, and so you don't actually need to add them.
| Category | Plugin | What it does |
|---|---|---|
| Tools | bash |
Run shell commands on the host |
docker-bash |
Run commands in a sandboxed Docker container | |
http |
Make HTTP requests to any web API | |
workspace |
Read/write files; browser build serves them as downloads | |
mcp |
Connect stdio (local) MCP servers; mcp-http adds HTTP/SSE servers (browser + Node) |
|
sessions |
List, rename, and hide saved sessions (session_action) |
|
edit-session |
Cut, fork, split, and compact sessions to manage context window size | |
ask-user |
Ask the user a question mid-turn (ask_user) |
|
whoami |
Report the current principal (whoami) |
|
function-tools |
Author & run TypeScript functions that compose other tools in one pass (tool_function) |
|
tool-store |
Define named persistent stores with generated CRUD tools (store_action) |
|
tool-router |
Serve the model a bounded per-turn window from a large tool library, with a tool_search entry point |
|
tool-types |
Node service: derives a typed .d.ts of tool results so tool-composing codegen (function-tools, skill-compiler) is type-checked |
|
| Hooks | json-validation |
Validate tool inputs against schema (a toolcall hook); LLM self-corrects on mismatch |
triggers |
Data-driven hooks: fire a tool or skill when an LLM classifier judges a stored condition matched | |
hook-logger |
Diagnostic: log every hook-channel firing | |
provenance |
Trace a claim back to the tool result or message it came from (determine_provenance) |
|
| Knowledge | rumsfeld |
Look up the knowledge index when the LLM encounters an unknown term |
persist-ki-bge |
Persistent knowledge index with optional BGE reranker | |
skills |
Named markdown playbooks, injected on demand by a classifier | |
skills-node |
Node specialization of skills: import & watch local .md skill files |
|
skill-compiler |
Compile a procedural skill into a hot-loadable TypeScript tool plugin (skill_compiler) |
|
cognition |
Inner-voice critique, persistent fact memory, and background Dream Time consolidation | |
| Frontends | frontend/web |
HTTP + SSE web UI with session management |
frontend/dom |
Minimal in-process browser chat (the matbot-demo.html demonstrator) |
|
frontend/telegram |
Telegram bot frontend | |
web-principal-user |
Set the web frontend's request principal to the host OS user — a template for multi-user auth | |
| Providers | anthropic |
Anthropic Messages API (+ DeepSeek /anthropic compat) |
openai-compat |
OpenAI-compatible chat completions | |
google |
Google Gemini (native generateContent) |
|
customer-services |
Free built-in demo LLM — no API key needed | |
chatjimmy |
Hosted llama endpoint — non-streaming, text-only, keyless; a very low-latency comparison point | |
| Storage | storage/filesystem |
Filesystem backend (Node; the CLI's default store) |
storage/sqlite |
SQLite backend (Node) | |
storage/google-drive |
Google Drive backend (browser) | |
storage/profiles |
Per-principal partitioning over the filesystem backend — a profile per user (profile_action, share) |
|
browser |
Browser-native storage: IndexedDB store, OPFS files, WebCrypto vault (+ the browser plugin/provider tools) |
|
files |
Node filesystem FileStore for MIME-typed blobs served by the web frontend |
|
| Background | background |
Detached background jobs and cron-style scheduling |
The built-in plugin and provider tools (add/remove/reload plugins, manage LLM profiles) are always
loaded — see Core above. Note: plugins are scoped to a run-time; not all (eg bash) are
available in all run-times (eg the browser).
The browser build
The browser build deserves its own section because it's not a cut-down version — it's a deliberate deployment target.
matbot's platform-neutral core and browser-safe plugins are type-stripped and bundled
into a single matbot.html. It runs entirely in-page: no server, no CORS proxy, no
service worker. The same web UI used by the Node web frontend is reused here, with an
in-process transport instead of HTTP+SSE.
What the browser build gives you:
- Zero install — open a URL, bring an API key, start working
- Sandboxed by default — browser security restrictions limit blast radius if your LLM goes rogue
- Persistent — sessions, skills, provider config, and workspace files survive page reloads
- Portable — loadable from a
file://URL or any static host
What it doesn't have (since they require Node specific interfaces):
- Bash / Docker execution
- Stdio MCP servers
- SQLite storage
- Telegram frontend
- Filesystem-backed skills with file watching
For evaluating or just basic personal use, the browser build is the right starting point.
Composing tools in code
LLMs are better at writing code than running it.
A long chain of one-at-a-time tool calls is where models drift: every round-trip is a chance to lose the thread, and the orchestration gets re-derived from scratch each turn. Writing a single function that calls those same tools is something models do far more reliably — so matbot lets them do exactly that, and leans hard on dynamically-generated TypeScript types to keep them honest.
The type surface is live. The tool-types
plugin derives a .d.ts of every currently-loaded tool from its declared contract — inputs and
results. Multi-action tools become overload sets, so await tool.workspace_action({ action: 'read', name })
narrows to the exact result type for that action. The model isn't guessing what a call returns; it's
reading it off the types.
The model writes functions that compose tools. With function-tools, the model authors a
TypeScript function that calls several tools in one pass — tool_function with define persists it as
a new named tool (reusable next turn), lambda runs it once. There's no build step: the TypeScript is
erased at runtime by the host's stripper, exactly as matbot's own plugins are.
Procedures compile to code. skill-compiler turns a written-out markdown procedure (a skill)
into an executable TypeScript tool plugin — a fuzzy prose playbook becomes a deterministic,
re-runnable tool.
Hallucinated shapes can't reach runtime. Every piece of generated code is graded by a real
TypeScript compiler running in a worker thread — including a cast gate that rejects as any /
as unknown as T escape hatches exactly like type errors, because a cast is the one hole a made-up
shape could slip through. Diagnostics are annotated and handed back to the model to repair. A tool
that type-checks is a tool whose calls resolve to the shapes the model expected.
Bonus feature: much lower cost Tools created by
tool_functionandskill_compilerare MUCH more efficient in terms of tokens because
- The cost of generating the code is done once, up-front, and re-used for free. Even if a tool uses a
single_turntool for LLM classification or summarization, it does so in a low-context, low cost environment. Your compiled skills and functional tools don't have to be dumb! - Tools which render large volumes of data and aggregate it for presentation to another tool doesn't require that the LLM tokenises all your data. This is huge win when your tools are orchestrating work based on external data sources, especially repeatedly.
The payoff: fewer round-trips, deterministic re-runs, and the model's discretion over things it can't actually know — a tool's return shape, a required parameter — compiled away instead of re-guessed every turn.
Writing a plugin
The plugin API is a TypeScript interface. A minimal tool plugin — declaring its typed call contract so the tool is type-safe end to end:
import type { MatbotPluginSpec, Tool, ToolContract, ToolResultOf } from '@matatbread/matbot-plugin-api'; import { PLUGIN_API_VERSION } from '@matatbread/matbot-plugin-api'; // The single source of truth for what `hello` accepts and returns. Augment ToolContracts // exactly as you would MarkerData / MatbotServices — keyed by the tool's name. declare module '@matatbread/matbot-plugin-api' { interface ToolContracts { hello: ToolContract<string, { name: string }>; // ToolContract<Result, Params> } } // Binding `Tool<ToolResultOf<'hello'>>` ties the executor to that contract: the compiler now // checks every `result` you yield against it, so the tool and its declared type can't drift. const myTool: Tool<ToolResultOf<'hello'>> = { name: 'hello', description: 'Greet someone.', inputSchema: { type: 'object', required: ['name'], properties: { name: { type: 'string' } }, }, executor: { async *execute(input) { const { name } = input as { name: string }; // inputSchema stays loose; validate at the boundary yield { type: 'result', value: `Hello, ${name}!` }; // ← must be a string, or this won't compile }, }, }; export const plugin: MatbotPluginSpec = { apiVersion: PLUGIN_API_VERSION, tools: [myTool], };
That one augmentation pays off everywhere: callers recover the concrete result from invokeTool +
toolResult instead of unknown, and the typed tool proxy that code-composed tools
call knows exactly what hello returns. Multi-action tools register a union of arms — one
ToolContract<Result, Params> per action — so the result narrows by the params passed; see
DEVELOPING.md for that pattern.
Add it to your config — or just tell the LLM:
Add the local plugin called my-plugin
Plugins can provide tools, frontends, LLM adapters, storage backends, knowledge indexes, hooks, or entirely new concepts (memory systems, LLM routing, background cognition — all shipped as first-party plugins in this repo).
More about matbot
| Document | What's in it |
|---|---|
| GETTING-STARTED.md | Installation, full CLI reference, config reference, worked examples |
| DEVELOPING.md | Full plugin API — tools, providers, storage, frontends, hooks, the browser bundle |
| ARCHITECTURE.md | Visual tour of the core, plugin seams, and turn flow |
| MEDIA.md | How images, PDFs and audio reach a model — submission, storage and delivery, with the first-party plugins as templates |
| SSE-CLIENTS.md | Writing a UI client (or reimplementing the server half): what the streams guarantee, and the failures that are invisible in testing |
| CLAUDE.md | Design ethos, hard rules, and architectural intent — written for AI assistants working on the codebase, but essential reading for any contributor |
Requirements
- Node 24.12+
- pnpm 9+ (only to run from source; a project install uses npm)
- An LLM API key (Anthropic, OpenAI-compatible, Google Gemini, DeepSeek, Ollama, …)
Licence
Apache 2.0
来源:GitHub · github.com