{"id":50151896,"url":"https://github.com/zombico/mojulo","last_synced_at":"2026-05-27T03:11:27.328Z","repository":{"id":355757349,"uuid":"1225207695","full_name":"zombico/mojulo","owner":"zombico","description":"Observable, auditable platform for conversational AI. Build multilingual, vector-supported, self-contained chatbot apps that can be deployed anywhere a Docker container can run.","archived":false,"fork":false,"pushed_at":"2026-05-22T06:30:58.000Z","size":61044,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-22T14:55:06.702Z","etag":null,"topics":["anthropic-claude","audit-log","audit-trail","chatbot","chatbot-application","chatbot-framework","docker","flyio","huggingface-transformers","multilingual","nodejs","ollama","openai-api","rag-chatbot","self-hosted-ai","sovereign-ai","sqlite","vector-search"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zombico.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","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":"2026-04-30T03:49:48.000Z","updated_at":"2026-05-22T06:31:03.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/zombico/mojulo","commit_stats":null,"previous_names":["zombico/mojulo-lite","zombico/mojulo"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/zombico/mojulo","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zombico%2Fmojulo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zombico%2Fmojulo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zombico%2Fmojulo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zombico%2Fmojulo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zombico","download_url":"https://codeload.github.com/zombico/mojulo/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zombico%2Fmojulo/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33427584,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-23T22:14:44.296Z","status":"online","status_checked_at":"2026-05-24T02:00:06.296Z","response_time":57,"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":["anthropic-claude","audit-log","audit-trail","chatbot","chatbot-application","chatbot-framework","docker","flyio","huggingface-transformers","multilingual","nodejs","ollama","openai-api","rag-chatbot","self-hosted-ai","sovereign-ai","sqlite","vector-search"],"created_at":"2026-05-24T09:00:20.581Z","updated_at":"2026-05-24T09:00:42.382Z","avatar_url":"https://github.com/zombico.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Mojulo\n\nMojulo is an MCP control plane for designing, deploying, and operating a fleet of self-hosted chat bots from your MCP-capable agent (Claude Code, Codex, Claude Desktop, or any other MCP host). You describe a bot in one sentence; mojulo compiles a runnable `\u003cbot\u003e.zip`; from then on your agent drives the build → deploy → connect → operate loop. Mojulo composes alongside the other MCP servers you already have installed (Drive, Gmail, your CRM), so signal from any deployed bot can route into the rest of your toolchain without leaving the agent loop.\n\nAs of `0.4`, mojulo also works without a bot. Declare your installed MCPs once and the same control plane composes MCP-orchestrated workflows over them — scheduled digests, signal-triggered automations, cross-tool wiring — with the **contextmap** recording *why* each artifact was materialized and the **mcp-orbit composer** assembling them from typed components. See [docs/meta-context.md](docs/meta-context.md) and [docs/mcp-orbit.md](docs/mcp-orbit.md).\n\nEach bot runs in its own Docker container and accumulates conversations in a local SQLite file, hash-chained turn by turn. The control plane stores only `url` + `last_seen_at` per bot — transcript content stays on the bot forever, read live through a bearer-authenticated proxy. The bot's config is plain JSON:\n\n```json\n{\n  \"name\": \"dental-triage\",\n  \"identity\": { \"name\": \"Smile Clinic Assistant\", \"tone\": \"warm, plainspoken\" },\n  \"enabledProtocols\": { \"knowledge\": true, \"formGathering\": true, \"triage\": true },\n  \"formGathering\": { \"fields\": [\"full_name\", \"dob\", \"insurance_carrier\", \"chief_complaint\"] },\n  \"triage\": { \"routes\": [{ \"label\": \"urgent — on-call coordinator\", \"destination\": \"...\" }] }\n}\n```\n\nYou can edit it, you can `cat` it, you can move the bot to another host by copying the zip. The bot is yours.\n\n\u003c!--\n  HERO IMAGE — put it here.\n  Recommended shot: an MCP-capable agent (Claude Code, Claude Desktop,\n  or Codex) mid-tool-call against mojulo. Ideally `forward_context` →\n  `infer_intent` → `save_modular_bot` in the transcript, with a fragment\n  of the resulting deployment row visible in the dashboard. The pitch is\n  \"your agent drives the loop\"; the image has to read that way at a glance.\n  Suggested filename: docs/images/hero-mcp-loop.png\n  Width: 100% / aspect ~16:9\n--\u003e\n![MCP-driven build loop](docs/images/hero-mcp-loop.png)\n\nThe control plane is usable via **app** and via **MCP** — two surfaces over the same encrypted config:\n\n- **MCP.** Point your MCP-capable agent (Claude Code, Claude Desktop, Codex, or any other MCP host) at mojulo and your agent drives the build/deploy/operate loop, composing mojulo's tools with the rest of your MCP servers. See [docs/mcp-integration.md](docs/mcp-integration.md).\n- **App.** Browser dashboard at `localhost:3001`. Paste your LLM and Fly.io API keys here — they get AES-encrypted at rest, and your agent never sees them; the MCP tools just consume them out of the store. The app also hosts a step-by-step wizard (and a conversational in-app builder) if you'd rather click through a build than describe it.\n\nBoth produce the same `\u003cbot\u003e.zip`.\n\n---\n\n## Quickstart\n\n### MCP\n\n```bash\n# 1. Wire mojulo into your MCP-capable agent.\n#    Claude Code / Claude Desktop:\nclaude mcp add mojulo --command \"npx -y mojulo\"\n#    Codex CLI: add to ~/.codex/config.toml\n#      [mcp_servers.mojulo]\n#      command = \"npx\"\n#      args = [\"-y\", \"mojulo\"]\n#    Any other MCP host: register the same `npx -y mojulo` stdio command.\n\n# 2. Configure at least one LLM provider key.\n#    Safer: paste it in the app's Settings → Provider Keys page (below).\n#    The CLI works too, but the key lands in your shell history.\n#    (mojulo-config ships inside the mojulo package, so -p mojulo is required)\nnpx -y -p mojulo mojulo-config set anthropic sk-ant-...\n\n# 3. In an agent session, ask:\n#    \"build me a triage bot for my dental practice\"\n```\n\nCompiled bots land in `~/.mojulo/data/artifacts/`. Run them with `docker compose up`, or set a Fly token (`npx -y -p mojulo mojulo-config set fly fo1_...`) and ask your agent to deploy to the cloud.\n\nWhen your agent first connects, it calls `forward_context` to read mojulo's concept glossary, lifecycle, and tool index — so the first session orients itself before doing anything destructive. Host adapters (`claude-code`, `codex`, `generic`) are auto-resolved from the connecting client; non-Claude agents should also read [AGENTS.md](AGENTS.md) for host-specific procedure.\n\n### App\n\nTo run the app (Settings UI for key paste, wizard, in-app builder, and the optional HTTP MCP route for remote clients):\n\n```bash\ngit clone https://github.com/zombico/mojulo.git\ncd mojulo/control\ncp .env.example .env\nnpm install\nnpm run fetch-models   # downloads the 113MB ONNX model for offline RAG (~30–60s)\nnpm run dev            # http://localhost:3001\n```\n\nPaste an LLM provider key under **Settings → Provider Keys**. To enable HTTP MCP for a remote agent (Claude Code, Codex, etc.), also set `CONTROL_PLANE_MCP_KEY` in `control/.env` — see [docs/mcp-integration.md](docs/mcp-integration.md).\n\n---\n\n## The loop: build → deploy → connect → operate\n\n**Build.** A bot's capabilities are called **protocols** — five of them ship: `knowledge` (in-process RAG), `formGathering` (structured field capture, PII bypasses the LLM), `appointments`, `triage` (cross-bot routing), `opticalRead` (vision-based extraction). To build a bot, pick which protocols it needs, upload any documents it should know from, and compose its identity. From your agent, describe the bot in free text and the build tools sequence themselves starting at `infer_intent`; in the app, the wizard or in-app builder walks the same steps.\n\n**Deploy.** `save_modular_bot` compiles the configured bot into a zip artifact. Run it locally (`docker compose up`), in the cloud (Fly.io from the dashboard or via MCP), or air-gapped with the source bundled in. The container image is bot-agnostic — per-bot config is injected at start time, so the same image runs every bot you have.\n\n**Connect.** Once a bot starts, it phones home to the control plane with its URL. From then on the control plane can reach it through a bearer-authenticated proxy. **Conversation data stays in the bot's SQLite forever** — the control plane only stores `url` and `last_seen_at`. Any tool that needs transcript content proxies through to the bot in real time.\n\n**Operate.** Read what bots have captured (`query_conversations`, `query_submissions`, `verify_chain`) or use catalysts — curated workflow recipes — to turn that captured signal into action via your other installed MCPs.\n\n---\n\n## What you get\n\nTwo terms recur below: **protocols** are the bot capabilities defined in *The loop* above (knowledge, form-gathering, appointments, triage, optical-read); **catalysts** are curated workflow recipes that your agent reads and turns into a runnable artifact for its host — a Claude Code skill under `.claude/skills/`, a Codex automation, or a generic `workflow.md`, depending on which agent is connected. Full glossary in [docs/mojulo-bots.md](docs/mojulo-bots.md).\n\n### As an MCP server\n\n- **Composable with the rest of your toolchain.** Drive folder → bot knowledge base. Linear escalations → triage routes. Intake submissions → CRM contact + welcome email + ticket. None of this is reachable from the in-app builders, because they can't see your other MCPs. See the recipes in [docs/mcp-integration.md](docs/mcp-integration.md).\n- **Catalysts.** `list_catalysts` exposes curated patterns — `qualify-lead-to-crm`, `appointment-to-calendar`, `submission-to-ticket`, `scan-conversations-for-signal`, `weekly-submissions-digest`, `knowledge-gap-miner`. Your agent reads one, binds it to a destination MCP you already have installed, and materializes a runnable artifact through the host adapter for its client (`claude-code`, `codex`, or `generic`). The catalyst stays in mojulo; the resulting artifact lives on your machine. See [docs/catalysts.md](docs/catalysts.md).\n- **The reasoning bill moves to your agent.** When you drive it from MCP, the control plane doesn't need a provider key for builder-time work — your agent is the agent loop. The in-loop LLM calls that *do* stay server-side (form generation, identity composition, bot summary) use whichever provider you configured.\n\n### As an artifact\n\n- **Hash-chained transcripts.** Every turn is content-hashed and chain-linked; `/verify/:id` walks the chain. Chains continue across triage handoffs — the receiver's first turn descends from the sender's tip-of-chain. Image-extraction turns hash over the image bytes, so post-hoc edits to the source image break the chain. See [docs/turn-hashing.md](docs/turn-hashing.md) and [docs/federated-routing.md](docs/federated-routing.md).\n- **Multilingual vector RAG, fully offline at runtime.** Knowledge documents and triage routes are embedded with `multilingual-e5-small` ONNX baked into the bot image. Cross-language retrieval works without a language-detection step or an embedding-API key — e.g. a Thai query against a Spanish corpus. See [docs/vector-rag.md](docs/vector-rag.md).\n- **Out-of-band forms — PII bypasses the LLM.** Locale-aware structured fields render client-side and submit through a dedicated endpoint that doesn't call the model. The transcript records only an opaque marker like `{contact_form_filled}`. See [docs/form-collection.md](docs/form-collection.md).\n- **Image extraction with hashed inputs.** Name the slots you want out of an uploaded image (DOB, license #, expiry, prescription dose); a vision-capable LLM reads the artifact, the user reviews and edits before submit. See [docs/optical-read.md](docs/optical-read.md).\n- **Multiple LLM providers.** OpenAI, Anthropic, or local Ollama — pick at build time, swap by editing `.env`. Cloud providers and llama3.3 (70B) run every protocol; smaller local models (qwen3, mistral-nemo) are gated to knowledge-only bots since multi-step tool use is unreliable on them.\n- **Localized bot UI and form validation across 20 locales** — the chat widget and form error messages render in the user's language without operator configuration.\n- **Embeddable widget, Prometheus metrics, form-submission webhooks.**\n\n---\n\n## Why\n\nMost chatbot builders are hosted SaaS — a managed widget, a recurring bill, no ownership of the artifact itself. The bot is something they run for you.\n\nMojulo produces an artifact instead. The bot you compile is yours: the source is a single open-source image, the config is plain JSON, conversations live in a SQLite file on the bot. The control plane builds it; the bot doesn't phone home for inference; the dashboard reads conversations live without copying them. And because mojulo is MCP-native, the build/deploy/operate loop is something **your** agent drives — Claude Code, Codex, or any other MCP host — not a UI you log into to click around.\n\n## Who builds with this\n\nA spectrum, all driving the same open-source, self-hosted stack from their own MCP-capable agent:\n\n- **Indie makers** shipping a side-project bot without a SaaS bill — describe it once, point the resulting artifact at a small VPS.\n- **Agencies** building a per-client bot per deployment, swapping LLM provider and locale per project, then wiring each client's bot into that client's CRM in the same agent session.\n- **Internal IT** rolling out an air-gapped helper inside a firewalled network — offline RAG means there's no embedding API to allow-list.\n- **Regulated SMBs** — clinics, law offices, financial pre-screen — where the tamper-evident transcript provides an internal audit trail (see [Audit chain posture](#audit-chain-posture) below for what's guaranteed and what isn't).\n\n---\n\n## Deploy options\n\n### Locally (default)\n\nThe compiled zip pulls a pinned bot image from GHCR and runs it. No build step on your laptop:\n\n```bash\nunzip my-bot-{id}.zip \u0026\u0026 cd my-bot-{id}\n# paste LLM key into .env\ndocker compose up\n```\n\n### Fly.io\n\nConfigure a Fly API token (`mojulo config set fly fo1_...`, or paste it in **Settings → Provider Keys**), then deploy from the dashboard or ask your agent to deploy via MCP. Persistent volume, autostart on request, autostop when idle. No `flyctl` install required. Your Fly account, your bill.\n\n### Air-gapped / your own registry\n\nSet `MOJULO_OFFLINE_BUILD=1` on the control plane. The artifact bundles full source + Dockerfile and builds locally on the target machine — no GHCR reachability required.\n\nTo point the prebuilt path at your own registry:\n\n```bash\nBOT_IMAGE=ghcr.io/your-org/your-bot:0.1.0           # control plane local build\nMOJULO_CLOUD_IMAGE=ghcr.io/your-org/your-bot:0.1.0  # Fly cloud deploy\n```\n\n---\n\n## Security \u0026 deployment posture\n\nThe control plane is **single-user, self-hosted**. Two access-control affordances, both opt-in:\n\n- **HTTP login** (for the dashboard UI). Set `CONTROL_PLANE_USER` + `CONTROL_PLANE_PASSWORD` in `control/.env`. Sessions are HMAC-signed with the password itself, so rotating the password invalidates every outstanding session with no extra bookkeeping. Intentionally minimal — no MFA, no lockout, no multi-user — and not a substitute for network isolation.\n- **MCP bearer token** (for HTTP MCP). Set `CONTROL_PLANE_MCP_KEY` to enable `/api/mcp`; with the key unset, the route 404s and the surface is invisible. One token, one user. The stdio transport (`npx -y mojulo`) is local-only and doesn't use this key.\n\n**Network posture:** don't expose the control plane to the public internet. Pick whichever fits:\n\n- **Run on `localhost`** (the default). Right for \"build a bot on my laptop, ship the artifact.\"\n- **Tailscale / WireGuard / VPN.** Reach the control plane only from your tailnet.\n- **SSH tunnel.** `ssh -L 3001:localhost:3001 your-host` for occasional remote access.\n- **Reverse proxy with auth in front.** Caddy, nginx, Traefik with basic auth — or OAuth2 Proxy, Cloudflare Access, Authelia, Tailscale Funnel.\n\n**The bots it compiles have a different posture** — they're designed to face end users. The control plane → bot read-through proxy is authenticated by a key both sides share (`MOJULO_API_KEY`, baked into the artifact at build time), and conversation data stays in the bot's local SQLite.\n\nFor the threat model and what does or doesn't count as a security issue, see [SECURITY.md](SECURITY.md).\n\n---\n\n## Audit chain posture\n\nThe per-turn hash chain (`content_hash` + `chain_hash`, walked by `/verify/:id`) is **tamper-evident, not tamper-proof**. It catches naive retroactive edits to the bot's SQLite — change one row, the chain breaks at every row after it. It does **not** stop a sophisticated operator with DB access from rebuilding a coherent forged history from scratch; there is no signing key and no external anchor.\n\nIf your threat model demands non-repudiation against the bot operator themselves, you need an external anchor — **RFC 3161 timestamping**, **OpenTimestamps (Bitcoin anchoring)**, or an **external witness server** that records chain tips out of band. None of these are shipped today; the federated-routing handoff is the existing externalization surface where a pluggable witness sink would land. See [docs/turn-hashing.md](docs/turn-hashing.md) for the full scope statement.\n\n---\n\n## Architecture in one paragraph\n\nThe control plane is a Next.js app exposing both a dashboard and an MCP server (stdio for the npm package, HTTP for remote clients).\n\nBuilder tools — driven from your agent over MCP or from the in-app chat builder / wizard — produce a deployment config (same shape regardless of entry point). From there, [DockerDeployer](control/lib/deployers/docker.js) composes a per-bot `instructions.txt` from protocol cartridges, bakes documents + triage routes into an `embeddings.json` vector index, and packages config + `docker-compose.yml` + `.env.example` into a zip.\n\nThe runtime is a separate Express container ([lite-template/](lite-template/)) published to GHCR — pull it, mount the per-bot config, you have a bot. Cloud deploys go to Fly Machines, injecting the same config files via the Machines API instead of a zip.\n\nThe dashboard reads conversations from connected bots live, through a bearer-authenticated proxy — transcript rows never get replicated into the control-plane DB.\n\nFull diagrams: [ARCHITECTURE.md](ARCHITECTURE.md).\n\n---\n\n## Repo layout\n\n```\nmojulo/\n├── control/        Next.js control plane: MCP server, dashboard, builders, deploy pipeline\n├── lite-template/  The bot itself: Express server, RAG, LLM client, Dockerfile\n└── ARCHITECTURE.md How it all fits together\n```\n\nPer-package docs: [control/README.md](control/README.md) — running the control plane in dev. [lite-template/](lite-template/) — bot runtime internals.\n\nConcept docs (start with the first three):\n\n- [docs/mojulo-bots.md](docs/mojulo-bots.md) — plain-language orientation to bots, protocols, and the control plane\n- [docs/mcp-integration.md](docs/mcp-integration.md) — the MCP surface, the composition recipes, the session model\n- [docs/catalysts.md](docs/catalysts.md) — what a catalyst is and how to author one\n- [docs/wizard-builder.md](docs/wizard-builder.md), [docs/chat-builder.md](docs/chat-builder.md) — the in-app build paths\n- [docs/vector-rag.md](docs/vector-rag.md), [docs/turn-hashing.md](docs/turn-hashing.md), [docs/federated-routing.md](docs/federated-routing.md) — the artifact properties\n- [docs/form-collection.md](docs/form-collection.md), [docs/optical-read.md](docs/optical-read.md), [docs/conversations-api.md](docs/conversations-api.md) — capture \u0026 read paths\n\n---\n\n## Contributing\n\n**One maintainer, no SLA.** Issues and PRs are read, but triage and review can take days or weeks depending on what's already in flight — a non-trivial PR may sit until I've had time to catch up on the surfaces it touches. Opening an issue first, even for a one-line PR, is the fastest path to a decision: it lets the scope conversation happen before the code does, so nobody's work waits in the queue for a \"no, retarget that.\"\n\nThe codebase is functionally modular but tightly integrated — a change to the envelope schema, the cartridge composer, a deployer, or the MCP tool surface touches multiple surfaces (control plane wizard, bot runtime, locales, model gates, catalyst contracts). That integration density is load-bearing for the artifact-portability and audit-chain guarantees, and it's also the reason contribution policy is channeled by surface rather than open across the board.\n\n**Always welcome — open an issue:**\n- Bug reports with a reproducer (especially RAG/locale/cartridge/MCP edge cases)\n- Translation quality issues (any locale, any string)\n- Documentation gaps or errors\n- Questions about whether something should be a PR\n\n**Accepted as PRs with the standard bar:**\n- Bug fixes with a clear reproducer (for non-obvious bugs, file an issue first so we can align on scope before you write the code)\n- Documentation fixes\n- Locale string fixes\n- Test additions that target the surfaces listed in [CONTRIBUTING.md](CONTRIBUTING.md#test-surface)\n\n**Forking \u0026 extending the platform:**\n- Custom protocols (your bot's specific behavior shape)\n- New provider adapters\n- Bespoke wizard flows or steps\n- Custom catalysts that don't merit promotion to the canonical library\n- Anything narrow to a client, vertical, or workflow\n\nThese belong in forks — the upstream repo stays abstract so the artifact format and audit guarantees stay stable. See [docs/protocol-composition.md#adding-a-new-protocol](docs/protocol-composition.md#adding-a-new-protocol) for the protocol recipe and [docs/catalysts.md](docs/catalysts.md) for the catalyst author spec.\n\nBefore opening a PR, read [ARCHITECTURE.md](ARCHITECTURE.md) so we're working from the same picture, and see [CONTRIBUTING.md](CONTRIBUTING.md) for the test surface, file layout, and pre-submit checklist.\n\n## License\n\n[Apache License 2.0](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzombico%2Fmojulo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzombico%2Fmojulo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzombico%2Fmojulo/lists"}