Skip to main content
Commands, package names, and image names on this page come from the open-source project that Mibyan Desktop is built on, and can differ from the Mibyan Desktop installer. For the supported Mibyan install and update path, see Install and update.
Memory provider plugins give Mibyan persistent, cross-session knowledge beyond the built-in MEMORY.md and USER.md. This guide covers how to build one.
Memory providers are one of two provider plugin types. The other is Context Engine Plugins, which replace the built-in context compressor. Both follow the same pattern: single-select, config-driven, managed via mibyan plugins.

Installation Layouts

Mibyan discovers memory providers from four sources, in this precedence order: Earlier sources win on a name collision, so a directory dropped into a working tree can never shadow a shipped provider.
This is the reverse of the general plugin system’s later-wins order. A memory provider is activated by name (memory.provider), so shadowing would silently redirect the agent’s memory rather than merely override a tool.
Discovery only enumerates — it never imports a provider. Nothing runs until memory.provider names it. Entry-point discovery does not install packages. Do not inject a provider into Mibyan’s selected environment with pip. On PM-managed installations, ship a directory provider with declared Python dependencies; plugin admission and mibyan memory setup prepare them through PM before use. Owner-managed builds (such as Nix) can include an entry-point distribution declaratively. CLI and dashboard setup share candidate preparation. PM includes the provider’s pyproject.toml or legacy pip_dependencies / python_dependencies alongside the active plugin union; an importable module does not bypass declared version constraints. Dashboard readiness checks the same inputs without installing anything. A successful preparation may require restarting Mibyan before the running process can use the selected dependency generation. External sidecar checks and setup commands remain separate from the Python union.

Directory Provider

A directory provider lives in plugins/memory/<name>/ when bundled with Mibyan, in $mibyan_HOME/plugins/<name>/ when installed by a user, or in ./.mibyan/plugins/<name>/ for a project-local one:

Packaged Provider

A pip-installed provider publishes an entry point in the mibyan_agent.memory_providers group. The entry-point name is the provider name users select in memory.provider; its value points to the provider’s register(ctx) function:
pyproject.toml
Point the entry point at the package, or at a register(ctx) inside it, and keep your implementation, skills, and other resources in the normal Python package layout. No copy under $mibyan_HOME/plugins/ is required. A package entry point gets everything a directory install does, including the two files Mibyan reads from disk rather than importing — config_schema.py (the dashboard config panel) and cli.py (your mibyan <provider> subcommands). Both are found next to your package’s __init__.py, so point the entry point at a package rather than a single module if you ship either.

The MemoryProvider ABC

Your plugin implements the MemoryProvider abstract base class from agent/memory_provider.py:

Initialization context

AIAgent passes session context through MemoryManager.initialize_all() to initialize(session_id, **kwargs). Accept **kwargs and tolerate missing optional fields; callers may initialize a provider without an agent or a session database. Do not assume os.getcwd() identifies the conversation’s workspace: one Desktop or gateway backend can serve several sessions. If cwd is absent and directory routing is needed, agent.runtime_cwd.resolve_agent_cwd() honors the session cwd context, then scoped terminal.cwd (carried internally as TERMINAL_CWD), then the launch directory. Construction-time workspace metadata does not require changing the process cwd or rebuilding an existing conversation’s system prompt. Desktop/TUI workspace changes synchronize the live agent’s session_cwd through tui_gateway/session_workdir.py::_register_session_cwd, including when a deferred agent is attached after a workspace move. This lets a first or restarted Codex app-server session use the current workspace instead of its construction-time cwd. It does not move an already-running Codex thread, reinitialize memory providers, change an existing Honcho session identity, or invalidate the cached system prompt. Provider initialization still receives the construction-time workspace; absent or empty cwd remains unpinned.

Required Methods

Core Lifecycle

Config

Optional Hooks

For native replace and remove, metadata["previous_content"] contains the full entry selected under the native-store lock. Notifications are emitted only after the complete write or batch succeeds. Batch notifications preserve operation order; each operation’s previous content reflects earlier operations in that batch. old_text is the caller’s search text, not the identity of the changed entry. Older Mibyan versions can omit previous_content. Providers that require exact identity should skip destructive mirroring when it is absent.

Oversized prefetch results

External prefetch() results above the configured spill threshold are written to a private spill file and replaced with the configured head/tail preview. The preview includes the path so the agent can read the full result when it is actually needed. Results at or below the threshold are returned unchanged. This uses the shared hooks.output_spill settings (10,000 characters by default); see Plugins — oversized-context spill.

Pre-Compress Checkpoints (fail-closed)

on_pre_compress() is best-effort by default: if your provider raises, the host logs the failure and compression proceeds. That is the right default for insight extraction — and the wrong one for a provider whose job is to archive transcript evidence to a durable store before the lossy rewrite. For that case the host offers an opt-in checkpoint contract (API v2):
Operators enable enforcement per deployment:
With the gate on, compression fails closed before any lossy rewrite unless an active provider advertising the API completed its checkpoint: the uncompressed transcript is preserved, the compaction attempt errors with BLOCKED_MISSING_PREREQUISITE, and it can be retried once your store recovers. With the gate off (default), nothing changes for existing providers. None of the providers bundled with Mibyan advertise checkpoint API v2 — the contract is opt-in and exists for third-party archiving providers. Enabling checkpoint_required without one therefore blocks every compression attempt (manual and automatic): agent init logs a warning naming the active provider, and each refusal names compression.checkpoint_required as the key to disable. The gate binds to every compaction authority, not just the Mibyan summarizer: server-side native compaction (compression.codex_responses_native) is suppressed while the gate is armed, post-turn micro-compaction (compression.micro_compact) is forced off at agent init (it absorbs old exchanges into a rolling summary with no checkpoint hook in its path), and the codex_app_server API mode is refused at agent init — the codex agent compacts its own thread with no truthful pre-compaction boundary, so a required checkpoint cannot be guaranteed there. The checkpoint-aware Mibyan compressor stays the only lossy authority. What your provider receives depends on its declared API version. Version 1 providers (the implicit default — every pre-existing provider) keep the historical contract: the raw message list, exactly as before. Version 2 checkpoint providers receive normalized direct evidence instead: user/assistant text rows only — tool results, system messages, the tool_calls payload of assistant messages (their prose is kept), and prior compaction summaries are filtered host-side. Prior summaries are recognized via a persistent _compressed_summary message marker that survives process restarts, so a resumed session never feeds derivative summaries back into your archive. Checkpoints must be idempotent. After a fail-closed block, the next compaction attempt calls on_pre_compress() again with the same transcript — and a transcript that grew only slightly produces largely overlapping evidence. Key your archive writes by content (for example a transcript digest) and upsert, so retries and overlaps deduplicate instead of accumulating duplicate archives. Contract tests: tests/agent/test_pre_compress_checkpoint_contract.py.

Setup UX — what a standalone provider keeps

Every setup surface Mibyan gives a bundled provider is driven by files in the provider’s own directory, so a provider installed from the plugin catalog keeps all of them: Your provider’s name, memory.<name> config section, data directory and tool names are the contract with existing users. A provider that moves out of core keeps all four; Mibyan then installs the catalog plugin automatically for anyone whose memory.provider still names it (on mibyan update, and once at agent start when security.allow_lazy_installs is on).

Config Schema

get_config_schema() returns a list of field descriptors used by mibyan memory setup:
Fields with secret: True and env_var go to .env. Non-secret fields are passed to save_config().
Minimal vs Full SchemaEvery field in get_config_schema() is prompted during mibyan memory setup. Providers with many options should keep the schema minimal — only include fields the user must configure (API key, required credentials). Document optional settings in a config file reference (e.g. $mibyan_HOME/myprovider.json) rather than prompting for them all during setup. This keeps the setup wizard fast while still supporting advanced configuration. See the Supermemory provider for an example — it only prompts for the API key; all other options live in supermemory.json.

Save Config

For env-var-only providers, leave the default no-op.

Plugin Entry Point

A provider may also expose read-only skills from the same callback. Skills are qualified by the entry-point name and are loaded only when that memory provider is active:
With the my-provider entry point active, the skill is available as my-provider:maintenance through skill_view().

plugin.yaml

Threading Contract

sync_turn() MUST be non-blocking. If your backend has latency (API calls, LLM processing), run the work in a daemon thread — spawned with agent.memory_provider.spawn_context_thread, never a bare threading.Thread. Profile isolation (the active mibyan_HOME, the per-turn secret scope) lives in contextvars, and a plain thread starts with an empty context: under multiplexed profiles it would silently write into the default profile’s store, and get_secret() fails closed there.
The same applies to prefetch and writer threads. Small JSON config sidecars ($mibyan_HOME/<provider>.json) are read with utils.read_json_or_empty and written with utils.atomic_json_write; anything under config.yaml goes through mibyan_cli.config.save_config(..., merge_existing=True). messages is optional OpenAI-style conversation context as of the completed turn. When present, it includes user/assistant messages, assistant tool calls, and tool result messages. Providers that do not need raw turn context can omit the messages parameter; Mibyan will continue calling them with the legacy signature. Cloud providers should document what parts of messages are sent off-device. Tool calls and tool results may contain file paths, command output, or other workspace data.

Profile Isolation

All storage paths must use the mibyan_home kwarg from initialize(), not hardcoded ~/.mibyan:

Testing

See tests/agent/test_memory_provider.py and adjacent memory tests (tests/agent/test_memory_session_switch.py, tests/agent/test_memory_user_id.py, tests/agent/test_memory_provider_init.py) for end-to-end patterns.

Adding CLI Commands

Memory provider plugins can register their own CLI subcommand tree (e.g. mibyan my-provider status, mibyan my-provider config). This uses a convention-based discovery system — no changes to core files needed.

How it works

  1. Add a cli.py file to your plugin directory
  2. Define a register_cli(subparser) function that builds the argparse tree
  3. The memory plugin system discovers it at startup via discover_plugin_cli_commands()
  4. Your commands appear under mibyan <provider-name> <subcommand>
Active-provider gating: Your CLI commands only appear when your provider is the active memory.provider in config. If a user hasn’t configured your provider, your commands won’t show in mibyan --help.

Example

Reference implementation

See plugins/memory/honcho/cli.py for a full example with 13 subcommands, cross-profile management (--target-profile), and config read/write.

Directory structure with CLI

Single Provider Rule

Only one external memory provider can be active at a time. If a user tries to register a second, the MemoryManager rejects it with a warning. This prevents tool schema bloat and conflicting backends.

mibyan_HOME survival contract (what wrappers can rely on)

For wrapper-style providers that keep their runtime in a sidecar venv outside Mibyan-managed Python (no dependency surface — no pyproject.toml, pip_dependencies, or python_dependencies — at the scanned plugin root; a pyproject.toml belonging solely to an external or nested sidecar is not scanned):
  • Location. $mibyan_HOME/plugins/<name>/ is the profile-scoped plugin location, and mibyan_HOME follows the active context override, then $mibyan_HOME, then the platform default. Propagate mibyan_HOME when launching the wrapper or sidecar so profile isolation holds; MemoryManager.initialize_all injects the active mibyan_home into every provider.
  • Survival. Ordinary Mibyan updates — including managed-venv rebuild/replacement by pm — do not delete or rewrite $mibyan_HOME/plugins/**. An installed wrapper directory and its marker file (e.g. mnemosyne-wrapper.json) survive. Explicit plugin updates and deletion flows (mibyan uninstall, mibyan plugins remove, profile deletion, user deletion) are excluded from this guarantee.
  • Sidecar isolation. A plugin root with no dependency surface never joins the pm workspace dependency union; a resync or venv rebuild neither provisions deps for it nor touches its tree.
  • Conflicts. For native shared-venv plugins, an unsatisfiable dependency union fails loudly: the candidate plugin stays unenabled and unimported (the admission authority refuses before publishing config, reporting the plugin identity plus the resolver’s reason, with a re-enable/retry path and a machine-readable pm receipt). Dependency resolution does not automatically disable other plugins or run a bisect. Explicit plugin updates, removal, and independent security gates are separate operations.