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.
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.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 inplugins/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 themibyan_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
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 theMemoryProvider 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
Externalprefetch() 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):
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:
secret: True and env_var go to .env. Non-secret fields are passed to save_config().
Save Config
Plugin Entry Point
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.
$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 themibyan_home kwarg from initialize(), not hardcoded ~/.mibyan:
Testing
Seetests/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
- Add a
cli.pyfile to your plugin directory - Define a
register_cli(subparser)function that builds the argparse tree - The memory plugin system discovers it at startup via
discover_plugin_cli_commands() - Your commands appear under
mibyan <provider-name> <subcommand>
memory.provider in config. If a user hasn’t configured your provider, your commands won’t show in mibyan --help.
Example
Reference implementation
Seeplugins/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, andmibyan_HOMEfollows the active context override, then$mibyan_HOME, then the platform default. Propagatemibyan_HOMEwhen launching the wrapper or sidecar so profile isolation holds;MemoryManager.initialize_allinjects the activemibyan_homeinto 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.

