# MuBit > A durable memory layer for AI agents — persistent across runs, queryable across sessions, with an explicit learning loop. import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## Changelog Overview User-visible MuBit documentation and integration updates. This changelog tracks user-visible documentation and integration guidance changes. ### 2026-04-17 — v0.6.0 — Flat client surface (breaking) #### SDKs (Python, JavaScript, Rust) * Every control-plane operation now lives directly on `Client`. Call `client.create_project(...)`, `client.set_prompt(...)`, `client.optimize_prompt(...)` — the `.control.` step is gone. * High-level helpers (`remember`, `recall`, `checkpoint`, `reflect`, `record_outcome`, …) are unchanged and continue to wrap the 10 identically-named raw ops with session resolution and richer defaults. Helper names win over the raw ops at name-collision sites. * Admin and low-level storage ops remain under `client.auth.*` and `client.core.*`. * **Migration:** global find-and-replace `client.control.` → `client.` and re-run tests. Three Core↔Control overlaps (`batch_insert`, `create_session`, `delete_run`) now resolve to the control version at `client.()`; call `client.core.()` explicitly for the core variant. * The Rust SDK also gains typed methods for 34 previously-missing control ops (Projects, Agent Definitions, Skills, Prompts, Sessions, Run History, activity listing/export, `record_step_outcome`, `get_run_ingest_stats`) — Rust is now at parity with Python and JavaScript. All three packages ship at **v0.6.0**. #### Version compatibility When upgrading `mubit-sdk` to `0.6.0`, upgrade the framework integration packages to their matching releases. The Python adapters all declare `mubit-integration-base>=0.5.1`, which in turn pins `mubit-sdk>=0.6.0`, so a mismatched upgrade will fail at install time rather than at runtime. | SDK | Version | Matching integrations | | ------------------------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `mubit-sdk` (Python) | `0.6.0` | `mubit-integration-base 0.5.1`, `mubit-crewai 0.5.1`, `mubit-langgraph 0.5.1`, `mubit-langchain 0.5.1`, `mubit-llama-index 0.5.1`, `mubit-adk 0.5.1`, `mubit-agno 0.5.1` | | `@mubit-ai/sdk` (JS) | `0.6.0` | `@mubit-ai/langgraph`, `@mubit-ai/ai-sdk`, `@mubit-ai/mcp` | | `mubit-sdk` (Rust crate) | `0.6.0` | — | Recommended upgrade flow: ```bash # Python pip install --upgrade mubit-sdk==0.6.0 \ mubit-integration-base==0.5.1 \ mubit-langgraph==0.5.1 mubit-langchain==0.5.1 \ mubit-crewai==0.5.1 mubit-adk==0.5.1 \ mubit-agno==0.5.1 mubit-llama-index==0.5.1 # JavaScript npm install @mubit-ai/sdk@0.6.0 \ @mubit-ai/langgraph @mubit-ai/ai-sdk @mubit-ai/mcp # Rust cargo add mubit-sdk@0.6.0 ``` ### 2026-04-17 — Managed resources + Project-first console #### New API documentation * **Projects, Agent Definitions, Skills, Prompts** are now documented as first-class managed resources. See [Projects, Agents, Skills, Prompts](/sdk/projects-and-agents). * **Prompt and Skill version lifecycle** — candidate → active promotion, LLM-powered optimization, version diff — are covered end-to-end. * New recipe: [Prompt Optimization Lifecycle](/recipes/prompt-optimization) — record outcomes → optimize → review diff → activate → rollback. * Control HTTP reference expanded with full route tables for `/v2/control/projects/*`, `/v2/control/projects/agents/*`, `/v2/control/skills/*`, `/v2/control/prompt/*`, and `/v2/control/sessions/*`. See [Control HTTP — Managed resources](/api-reference/control-http#managed-resources-projects-agents-skills-prompts). * Control gRPC reference expanded with the matching RPC groups (`CreateProject`/`...`, `SetPrompt`/`OptimizePrompt`/`ActivatePromptVersion`/`GetPromptDiff`, `CreateSkill`/`OptimizeSkill`/`...`, `CreateControlSession`/`...`). See [Control gRPC — Managed resources](/api-reference/control-grpc#managed-resources-projects-agents-skills-prompts). #### Deprecations (no runtime impact) * Goals, Actions, and Decision Cycle routes (`/v2/control/goals/*`, `/v2/control/actions/*`, `/v2/control/cycles/*`) are now formally marked **deprecated** in [State management](/api-reference/state-management). Existing deployments continue to work; new integrations should track goals/actions in external task and orchestration systems (Linear, LangGraph, CrewAI) and feed resulting lessons / outcomes into MuBit. * Variables and Concepts remain supported. #### Console * The user console is now organized around **Projects** as the top-level concept, with Agent Cards, Prompt versioning, Skill versioning, per-project Memory, Sandbox, Logs, and Settings. The SDK surface is unchanged — the new resource model is what the console renders against. ### 2026-03-23 — v0.5.1 (Hindsight features + scaling) #### New SDK features * **Temporal range queries** — `recall(min_timestamp=, max_timestamp=)` filters evidence by when events occurred, not when they were ingested. See [temporal queries](/sdk/sdk-methods#temporal-queries). * **Occurrence time tracking** — `remember(occurrence_time=)` records when an event happened, separate from ingestion time. See [occurrence time](/sdk/sdk-methods#occurrence-time). * **Search budget control** — `recall(budget="low"|"mid"|"high")` controls the latency/quality tradeoff. See [search budget](/sdk/sdk-methods#search-budget). * **Staleness metadata** — Query evidence now includes `is_stale` and `superseded_by` fields. Stale entries are automatically deprioritized in ranking. See [staleness detection](/sdk/sdk-methods#staleness-detection). * **Mental model entry type** — `remember(intent="mental_model")` stores consolidated entity summaries that are prioritized over raw facts in context assembly. See [mental models](/sdk/sdk-methods#mental-models). #### Server improvements * Per-run disk-backed index pools — memory stays bounded regardless of data volume. * LLM telemetry with Prometheus metrics (`mubit_llm_calls_total`, `mubit_llm_tokens_total`, `mubit_llm_call_duration_seconds`). * Connection pooling, write tuning, and client-side LLM rate limiting for 200+ concurrent agent workloads. * Event stream trimming, storage health monitoring, and disk usage metrics. * Sanitized error messages — no internal implementation details exposed to SDK clients. * New `xlarge` instance plan tier (8 CPU, 16 GiB) for high-throughput workloads. #### No breaking changes All new fields are optional with sensible defaults. Existing code continues to work without modification. ### 2026-03-16 — Documentation sync * Added SDK Configuration Reference page covering env vars, transport selection, and endpoint defaults. * Added gRPC Transport Guide explaining when to use gRPC vs HTTP, endpoint configuration, and TLS. * Added Activity & Audit Trail documentation for `listActivity`, `exportActivity`, and `appendActivity`. * Added missing SDK methods to the SDK methods reference: activity, ingest job tracking, run management, context snapshot. * Added missing HTTP routes to the Control HTTP reference: activity, runs, ingest stats, heartbeat, context/snapshot. * Added missing gRPC RPCs to the Control gRPC reference: full variable, concept, action, and cycle RPCs. * Added core route reference to Core Direct Lanes: SDM, scratchpad, sessions, storage, ACL, PubSub. * Added Troubleshooting / FAQ page covering common issues. * Fixed GitHub links in Framework Integrations from `anthropics/ricedb` to `mubit-ai/ricedb`. ### 2026-03-14 — v0.4.1 (JS SDK patch) * Fixed `keepCase` gRPC field casing in the JavaScript SDK to preserve proto field names. ### 2026-03-14 — Integration packages v0.1.0 * Published framework integration packages: * Python: `mubit-crewai`, `mubit-langgraph`, `mubit-langchain`, `mubit-adk` * JavaScript: `@mubit-ai/langgraph`, `@mubit-ai/ai-sdk`, `@mubit-ai/mcp` * TLS enabled on `api.mubit.ai` and `api.dev.mubit.ai`. **Subsequent additions (shipped at 0.5.1 alongside the SDK v0.6.0 release):** `mubit-integration-base` (shared client used by every Python adapter), `mubit-llama-index` (chat-memory + vector store), and `mubit-agno` (MemoryDb + Toolkit). See the [Version compatibility](#version-compatibility) table under v0.6.0. ### 2026-03-13 — v0.4.0 (MAS Features) * Added step-level outcome recording: `RecordStepOutcome` RPC, `/v2/control/step_outcome` HTTP route, `record_step_outcome()` SDK helper. Records per-step process reward signals (signal, rationale, directive hint) for dense RL within a run. * Added lane-scoped memory: `lane` field on `IngestItem`, `lane_filter` on queries and context assembly, `shared_memory_lanes` on agent registration. Enables multi-agent memory isolation within a shared run. * Added step-wise reflection: `step_id`, `last_n_items`, and `include_step_outcomes` fields on `ReflectRequest` for targeted, incremental lesson extraction scoped to recent evidence or a specific step. * Added auto-extraction in `mubit.learn`: heuristic extraction of rules, lessons, preferences, and facts from LLM responses without an extra LLM call (`auto_extract=True` in learn config). * New cookbook: [Step-Level Outcomes and Process Rewards](/recipes/step-level-outcomes). * New cookbook: [Lane-Scoped Multi-Agent Memory](/recipes/lane-scoped-memory). ### 2026-02-19 * Rebuilt the first tab as an SDK-first information architecture rooted at `/` and `/sdk/*`. * Removed legacy first-tab routes under `/developer-platform/*`, `/sdk-guides/*`, `/external-integrations/*`, `/hdql/*`, and `/getting-started/*`. * Removed catch-all route redirects so retired first-tab URLs now hard-retire instead of forwarding. * Added a full SDK method catalog page with all contract operations (auth, control, and core) mapped across SDK method names. * Rewrote onboarding and basics pages with richer technical guidance while preserving concise execution steps. * Kept canonical endpoint guidance on `MUBIT_ENDPOINT` + `MUBIT_API_KEY` with optional HTTP/gRPC endpoint overrides. * Updated machine-facing docs entry points in `agents.md` and `llms.txt` to the new SDK navigation model. ### 2026-02-20 * Expanded SDK/core value-prop coverage to explicitly include low-latency retrieval behavior, associative context grouping, and state-guardian control patterns. * Added deep-dive cookbook guides: * `/recipes/event-driven-agents` * `/recipes/branching-memory` * `/recipes/stateful-task-trees` * Added linked-run and event-stream coverage in SDK/API pages, including `include_linked_runs`, `link_run`/`unlink_run`, and control event subscription usage. * Added coverage for core pub-sub/session/scratchpad/maintenance capabilities with policy-boundary caveats for direct `/v2/core/*` access. * Corrected cookbook payload field naming from `target_run_id` to implemented `linked_run_id`. ### Next steps * Review SDK onboarding at [/](/). * Review API mapping at [Control HTTP reference](/api-reference/control-http). import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## Concepts How MuBit's memory model works, what the learning loop does, and how it compares to vector DBs, prompt caches, and RAG. MuBit's job is to give AI agents memory that persists, evolves, and is queryable across runs. This page is an evaluator's overview — read it to decide whether MuBit fits your problem before you write any integration code. ### The memory model MuBit organizes memory by **entry type**. Each type has different retention, retrieval, and access semantics. | Entry type | What it stores | Lifetime | Recall scope | | ----------- | --------------------------------------------------------------------------- | ------------------------------------------ | ---------------------------------------- | | **fact** | Atomic statements grounded in observation (e.g. "Customer Taylor uses SSO") | Run-scoped by default | Same `session_id` + `agent_id` | | **lesson** | Generalized takeaways from runs (e.g. "Always flag bare `except:`") | Configurable: `run` / `session` / `global` | Cross-session if `lesson_scope="global"` | | **rule** | Hard constraints that must always apply (e.g. "Never PII-log a `user.ssn`") | Persistent | Cross-session | | **trace** | Per-call records of what happened (LLM call, tool call, response) | Run-scoped | Same `session_id` | | **archive** | Exact artifacts you'll need byte-for-byte later (diffs, SQL, raw outputs) | Persistent | Cross-session via `reference_id` | Storing a fact and asking the wrong session to recall it returns nothing. Storing a global lesson and asking any session keyed by the same `user_id` returns it. This is the most common shape of "why didn't my recall work" — see [SDK helpers → cross-session recall](/sdk/sdk-helpers#cross-session-recall). ### The learning loop The four-step loop is what differentiates MuBit from a passive store. Read [How MuBit works](/sdk/how-mubit-works) for the full discussion. Your agent calls `remember()` to store facts, traces, or observations as they happen. Before the next LLM call, `recall()` finds relevant evidence and `get_context()` assembles a token-budgeted context block. After a run completes, `reflect()` extracts reusable lessons from what happened. Recurring lessons get promoted from run-scoped to session-scoped to global. `record_outcome()` feeds success / failure signals back into the lesson store, strengthening what worked and weakening what didn't. The agent gets better over multiple runs without retraining the model. ### How MuBit compares | If you need… | Reach for… | MuBit's role | | ------------------------------------------------ | --------------------------------------------------- | ------------------------------------------------------------------------------------------------- | | Similarity search over a static corpus | A vector DB (Pinecone, Weaviate, pgvector) | Not the right tool — MuBit is for **mutable** memory with outcomes | | Prompt-level caching of identical requests | A prompt cache (Anthropic prompt cache, Helicone) | Orthogonal — MuBit can sit alongside | | Stateless RAG against a fixed knowledge base | A RAG framework (LlamaIndex, LangChain) | Use the framework + MuBit as its persistent store via the [adapters](/sdk/framework-integrations) | | Memory that persists across sessions and evolves | **MuBit** | The whole product | | Multi-agent coordination with scoped access | **MuBit** (`register_agent`, `handoff`, `feedback`) | Native primitives | ### Trust and operations * **Authentication.** All calls use a bearer API key formatted as `mbt___`. The instance segment routes to a region; the key id is safe to log; the secret is not. See [Authentication](/sdk/authentication). * **Scopes.** Each agent registers with explicit `read_scopes` and `write_scopes` (`fact`, `lesson`, `rule`, `archive_block`, etc). The runtime enforces them. * **Rate limits.** Per-instance, per-route. See [Rate limits](/sdk/rate-limits). * **Audit.** Every write produces an activity entry. See [Activity & audit trail](/sdk/activity-audit). * **Data residency / SOC 2.** See [mubit.ai/security](https://mubit.ai/security). ### Where to go next The full memory model, control-plane vs. core-plane, query and context controls, and a worked helper-first example. Decided to integrate? Make your first authenticated call in under 60 seconds. Plug MuBit into CrewAI, LangGraph, LangChain, LlamaIndex, ADK, Agno, Vercel AI SDK, or MCP. Plans and per-instance limits. import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## Quickstart Make your first authenticated MuBit call in under 60 seconds. The terminal on the right runs the same code — a real round-trip against `api.mubit.ai` showing every payload the SDK returns. Switch the terminal between Python, Node, and Rust with the buttons in the title bar.
### Get your API key Sign in to the [MuBit console](https://console.mubit.ai) and create a key from **Settings → API keys**. Keys look like `mbt___` — three segments separated by underscores. The instance segment routes your call to the right region; the key id is safe to log; the secret is not. ```bash title=".env" MUBIT_API_KEY="mbt___" MUBIT_ENDPOINT="https://api.mubit.ai" ``` The SDK reads both env vars on `Client()`. Set `MUBIT_TRANSPORT="grpc"` if you need lower latency from a backend service — see [gRPC Transport](/sdk/grpc-transport). ### Install ```bash pip install mubit-sdk python-dotenv ``` ```bash npm install @mubit-ai/sdk dotenv ``` ```bash cargo add mubit-sdk dotenvy tokio --features tokio/full ``` ```bash bun add @mubit-ai/sdk # Bun pnpm add @mubit-ai/sdk # pnpm yarn add @mubit-ai/sdk # Yarn deno add npm:@mubit-ai/sdk # Deno ``` ### Authenticate and make your first call The script below stores a memory in one session and recalls it from a second session — proving cross-session memory works without any local cache. The right-hand terminal runs this exact flow. ```python title="v1_support.py" import os from dotenv import load_dotenv import mubit load_dotenv() client = mubit.Client(endpoint=os.environ["MUBIT_ENDPOINT"]) client.set_api_key(os.environ["MUBIT_API_KEY"]) client.remember( session_id="support:taylor:s1", agent_id="support-agent", user_id="taylor-1", content="Taylor prefers concise written updates on Friday afternoons; no phone calls.", intent="lesson", lesson_scope="global", ) answer = client.recall( session_id="support:taylor:s2", agent_id="support-agent", user_id="taylor-1", query="how does Taylor like updates?", entry_types=["lesson"], ) print(answer["final_answer"]) ``` ```js title="v1_support.mjs" import "dotenv/config"; import { Client } from "@mubit-ai/sdk"; const client = new Client({ endpoint: process.env.MUBIT_ENDPOINT }); client.setApiKey(process.env.MUBIT_API_KEY); await client.remember({ session_id: "support:taylor:s1", agent_id: "support-agent", user_id: "taylor-1", content: "Taylor prefers concise written updates on Friday afternoons; no phone calls.", intent: "lesson", lesson_scope: "global", }); const answer = await client.recall({ session_id: "support:taylor:s2", agent_id: "support-agent", user_id: "taylor-1", query: "how does Taylor like updates?", entry_types: ["lesson"], }); console.log(answer.final_answer); ``` ```rust title="src/main.rs" use mubit_sdk::{Client, ClientConfig, RecallOptions, RememberOptions}; #[tokio::main] async fn main() -> anyhow::Result<()> { dotenvy::dotenv().ok(); let client = Client::connect(ClientConfig::from_env()?).await?; let mut r = RememberOptions::new( "Taylor prefers concise written updates on Friday afternoons; no phone calls." ); r.session_id = Some("support:taylor:s1".into()); r.agent_id = Some("support-agent".into()); r.user_id = Some("taylor-1".into()); r.intent = Some("lesson".into()); r.lesson_scope = Some("global".into()); client.remember(r).await?; let mut q = RecallOptions::new("how does Taylor like updates?"); q.session_id = Some("support:taylor:s2".into()); q.agent_id = Some("support-agent".into()); q.user_id = Some("taylor-1".into()); q.entry_types = vec!["lesson".into()]; let answer = client.recall(q).await?; println!("{}", answer["final_answer"]); Ok(()) } ``` ```bash curl -X POST https://api.mubit.ai/v2/control/remember \ -H "Authorization: Bearer $MUBIT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "session_id": "support:taylor:s1", "agent_id": "support-agent", "user_id": "taylor-1", "content": "Taylor prefers concise written updates on Friday afternoons; no phone calls.", "intent": "lesson", "lesson_scope": "global" }' curl -X POST https://api.mubit.ai/v2/control/recall \ -H "Authorization: Bearer $MUBIT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "session_id": "support:taylor:s2", "agent_id": "support-agent", "user_id": "taylor-1", "query": "how does Taylor like updates?", "entry_types": ["lesson"] }' ``` A successful `recall` returns roughly: ```json { "final_answer": "Taylor prefers to receive updates in a concise written format delivered on Friday afternoons, and explicitly dislikes receiving phone calls.", "confidence": 1.0, "mode": "agent_routed", "evidence": [ { "id": "f7bfcb26-…", "score": 1.0, "content": "Taylor prefers concise written updates on Friday afternoons; no phone calls." } ] } ``` Didn't work? See [Troubleshooting](/sdk/troubleshooting). The two most common causes are a stale `MUBIT_API_KEY` and an HTTP/gRPC endpoint mismatch. **Cross-session footgun:** facts (`intent="fact"`) are session-local. To recall a memory in a different session keyed by `user_id`, store it as a `lesson` with `lesson_scope="global"` — that's why the example above uses `intent="lesson"`. See [SDK helpers](/sdk/sdk-helpers#cross-session-recall) for the full set. ### Pick your path Most readers want the drop-in. Switch only when you outgrow it. `mubit.learn.init()` auto-instruments your existing `anthropic` / `openai` / `litellm` / `google-genai` calls. Lessons inject before the call, reflection runs on session end. Call `remember`, `recall`, `get_context`, `archive`, `dereference` yourself when you want explicit control over sessions, agents, and metadata. Plug MuBit into the framework's own memory interface. CrewAI, LangGraph, LangChain, LlamaIndex, ADK, Agno, Vercel AI SDK, MCP. #### Drop-in: `mubit.learn` ```python title="learn_quickstart.py" import os import mubit.learn import anthropic mubit.learn.init( api_key=os.environ["MUBIT_API_KEY"], agent_id="code-reviewer", auto_reflect=True, ) # Existing Anthropic call now learns: lessons inject pre-call, reflection runs on session end. resp = anthropic.Anthropic().messages.create( model="claude-haiku-4-5-20251001", max_tokens=300, messages=[{"role": "user", "content": "Review this diff..."}], ) ``` `mubit.learn.init()` patches `anthropic.Anthropic`, `openai.OpenAI`, LiteLLM, and Google GenAI client classes in place. See [LLM provider support](/sdk/llm-providers) for the full matrix and per-provider notes. ### Production checklist Wire these up before you ship. * **Errors and error codes** → [/api-reference/errors](/api-reference/errors) * **Retries and idempotency** → [/sdk/retries](/sdk/retries) * **Rate limits and headers** → [/sdk/rate-limits](/sdk/rate-limits) * **Webhooks** (delivery + signing) → [/sdk/webhooks](/sdk/webhooks) * **Authentication and key rotation** → [/sdk/authentication](/sdk/authentication) * **Observability and audit trail** → [/sdk/activity-audit](/sdk/activity-audit) ### What to do next * Inspect the full helper surface in [SDK helpers](/sdk/sdk-helpers) — `remember`, `recall`, `get_context`, `archive`, `dereference`. * Add multi-agent coordination with [register\_agent + handoff + feedback](/recipes/multi-agent-shared-state). * Use [temporal queries](/sdk/sdk-methods#temporal-queries) to filter by when events happened, not when they were ingested. * Plug into your framework via [Framework integrations](/sdk/framework-integrations). * Browse the full control surface at [Control HTTP reference](/api-reference/control-http).
Live capture from `mubit_run/v1_support.py` against `api.mubit.ai`. Switch language with the buttons in the title bar.
import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## Activity & Audit Trail Browse, export, and append chronological activity entries for observability and compliance. MuBit tracks all memory operations as a chronological activity trail. Use it for observability, debugging, compliance audit, and manual trace annotation. Activity is distinct from semantic retrieval (`recall` / `query`). It provides exact chronological ordering — what happened and when — rather than ranked relevance. ### Browse activity List recent activity entries for a run with optional type and agent filters. ```python # List recent activity activity = client.list_activity( run_id="support:acme:ticket-42", limit=50, entry_types=["fact", "lesson"], ) for entry in activity["entries"]: print(f"{entry['timestamp']} [{entry['type']}] {entry['content'][:80]}") ``` ```js const activity = await client.listActivity({ runId: "support:acme:ticket-42", limit: 50, entryTypes: ["fact", "lesson"], }); for (const entry of activity.entries) { console.log(`${entry.timestamp} [${entry.type}] ${entry.content.slice(0, 80)}`); } ``` ### Export activity Export the full activity trail as JSONL for offline analysis or compliance archival. ```python export = client.export_activity( run_id="support:acme:ticket-42", format="jsonl", ) # export contains JSONL-formatted activity entries ``` ```js const exported = await client.exportActivity({ runId: "support:acme:ticket-42", format: "jsonl", }); ``` ### Append activity Manually append activity traces — useful for logging external observations, human annotations, or system events that the SDK doesn't capture automatically. ```python client.append_activity( run_id="support:acme:ticket-42", entries=[ {"type": "observation", "content": "Customer escalated via phone"}, {"type": "action", "content": "Transferred to senior support"}, ], agent_id="support-agent", ) ``` ```js await client.appendActivity({ runId: "support:acme:ticket-42", entries: [ { type: "observation", content: "Customer escalated via phone" }, { type: "action", content: "Transferred to senior support" }, ], agentId: "support-agent", }); ``` ### Activity vs. semantic query | | `listActivity` | `recall` / `query` | | ------------ | -------------------------------- | --------------------------------- | | **Ordering** | Chronological (exact timestamps) | Relevance-ranked | | **Use case** | Audit, debugging, compliance | Context retrieval for LLM prompts | | **Filters** | Type, agent, time range | Semantic similarity, entry types | | **Output** | Raw entries as ingested | Answer with evidence scoring | Use activity for "what happened" questions. Use query/recall for "what's relevant" questions. ### API reference | Transport | Endpoint | | --------- | ------------------------------------------------------------------------------------------------------------------------------ | | HTTP | `POST /v2/control/activity` (list), `POST /v2/control/activity/export` (export), `POST /v2/control/activities/append` (append) | | gRPC | `ListActivity`, `ExportActivity`, `AppendActivity` | See [Control HTTP reference](/api-reference/control-http) and [Control gRPC reference](/api-reference/control-grpc) for full request/response schemas. ### Next steps * See [SDK methods](/sdk/sdk-methods) for the full method catalog. * See [Control HTTP reference](/api-reference/control-http#activity-routes-request-shapes) for request/response field details. import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## Add data Use helper-first writes for normal MuBit memory flows, and fall back to raw ingest only when you need explicit async ingest control. Most MuBit integrations should start with `remember()`. Use raw `control.ingest` only when you need explicit async ingest behavior, job polling, or bulk payload control. For normal agent memory writes, the helper path is simpler and stays aligned with the current SDK contract. ### Recommended write contract 1. Keep one stable `session_id` / `run_id` per workflow, claim, ticket, or task. 2. Use `remember()` for facts, traces, lessons, rules, handoffs, feedback, and tool artifacts. 3. Use raw `control.ingest` when you need explicit async ingestion lifecycle control. 4. Stamp `agent_id`, `intent`, and metadata so later diagnose, reflect, and context assembly stay useful. ### Helper-first write examples ```python title="add_data_helper.py" def add_data_helper(client, run_id: str, text: str): client.remember( session_id=run_id, agent_id="support-agent", content=text, intent="fact", metadata={"source": "support", "channel": "ticket"}, ) ``` ```ts title="add_data_helper.ts" export async function addDataHelper(client, runId: string, text: string) { await client.remember({ session_id: runId, agent_id: "support-agent", content: text, intent: "fact", metadata: { source: "support", channel: "ticket" }, }); } ``` ```rust title="add_data_helper.rs" use mubit_sdk::RememberOptions; use serde_json::json; pub async fn add_data_helper( client: &mubit_sdk::Client, run_id: &str, text: &str, ) -> Result<(), Box> { let mut remember = RememberOptions::new(text); remember.run_id = Some(run_id.to_string()); remember.agent_id = Some("support-agent".to_string()); remember.intent = Some("fact".to_string()); remember.metadata = Some(json!({"source":"support","channel":"ticket"})); client.remember(remember).await?; Ok(()) } ``` ### When raw ingest is still the right choice Use raw `client.ingest` when you need one of these explicitly: * async job polling through `get_ingest_job` * controlled batch writes through `/v2/control/ingest` or `/v2/control/batch_insert` * wire-level debugging of serialized payloads * compatibility with systems that already generate ingest payload JSON directly ### Failure modes and troubleshooting | Symptom | Root cause | Fix | | ------------------------------------- | -------------------------------- | --------------------------------------------------------- | | Important memory never shows up later | `intent` or metadata missing | Tag writes with `intent`, `agent_id`, and useful metadata | | Helper writes feel too implicit | You need job-level control | Switch that path to raw `control.ingest` | | Cross-task contamination | `session_id` changes per request | Keep one deterministic task/session mapping | ### Next steps * Read with [Retrieve data](/sdk/retrieve-data). * Use the helper catalog at [SDK methods](/sdk/sdk-methods). import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## Agent State & Goals Manage run-scoped variables, goals, actions, and cycle history with MuBit control APIs. Semantic memory alone is not enough for long-running agents. Explicit state and planner history make behavior testable and auditable. ### Mental model * Variables hold explicit machine-readable state. * Goals define outcomes and priority. * Actions and cycles record planner decisions over time. * Logs and snapshots make behavior inspectable. ### Minimal implementation example ```python title="04-state-and-goals.py" client.set_variable({ "run_id": run_id, "name": "resolution_stage", "value_json": '{"stage":"needs-billing-review"}', "source": "reasoning", }) goal = client.add_goal({ "run_id": run_id, "description": "Resolve ticket with grounded invoice explanation", "priority": "high", }) client.submit_action({ "run_id": run_id, "agent_id": "support-agent", "action_type": "retrieve", "action_json": '{"query":"collect invoice discrepancy details"}', }) cycle = client.run_cycle({ "run_id": run_id, "agent_id": "support-agent", "candidates": [{ "action_type": "reason", "action_json": '{"prompt":"choose safest next response step"}', "score": 0.89, "rationale": "preserve accuracy before response", }], }) print(goal.get("id"), cycle.get("cycle_number")) ``` ```ts title="agent_state_and_goals.ts" await client.setVariable({ run_id: runId, name: "resolution_stage", value_json: '{"stage":"needs-billing-review"}', source: "reasoning", }); const goal = await client.addGoal({ run_id: runId, description: "Resolve ticket with grounded invoice explanation", priority: "high", }); await client.submitAction({ run_id: runId, agent_id: "support-agent", action_type: "retrieve", action_json: '{"query":"collect invoice discrepancy details"}', }); const cycle = await client.runCycle({ run_id: runId, agent_id: "support-agent", candidates: [ { action_type: "reason", action_json: '{"prompt":"choose safest next response step"}', score: 0.89, rationale: "preserve accuracy before response", }, ], }); console.log(goal.id, cycle.cycle_number); ``` ```rust title="agent_state_and_goals.rs" use serde_json::json; client.set_variable(json!({ "run_id": run_id, "name": "resolution_stage", "value_json": "{\"stage\":\"needs-billing-review\"}", "source": "reasoning" })).await?; let goal = client.add_goal(json!({ "run_id": run_id, "description": "Resolve ticket with grounded invoice explanation", "priority": "high" })).await?; client.submit_action(json!({ "run_id": run_id, "agent_id": "support-agent", "action_type": "retrieve", "action_json": "{\"query\":\"collect invoice discrepancy details\"}" })).await?; let cycle = client.run_cycle(json!({ "run_id": run_id, "agent_id": "support-agent", "candidates": [{ "action_type": "reason", "action_json": "{\"prompt\":\"choose safest next response step\"}", "score": 0.89, "rationale": "preserve accuracy before response" }] })).await?; println!("{:?} {:?}", goal["id"], cycle["cycle_number"]); ``` ### Failure modes and troubleshooting | Symptom | Root cause | Fix | | --------------- | ---------------------------------- | ------------------------------------- | | State conflicts | Writes spread across multiple runs | Keep deterministic run identity | | Goal sprawl | No parent/priority discipline | Define team goal policy | | Opaque behavior | Action/cycle logs not reviewed | Add routine log checks in dev and ops | ### Next steps * Add event-driven wakeups at [Pub-sub and events](/sdk/pubsub-and-events). * Split planner/specialist runs at [Sessions and branching](/sdk/sessions-and-branching). import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## Agent-decided Retrieval vs RAG Why MuBit lets the agent decide retrieval mode and compartment instead of forcing you to hard-code a RAG pipeline. Traditional RAG forces every retrieval decision into application code: you pick the index, choose the similarity metric, set the top-k, decide which documents to stuff into the prompt, and wire it all together before the model ever sees the query. When requirements change you re-engineer the pipeline. MuBit takes the opposite approach. The agent decides the retrieval mode and the memory compartment at query time. You call `control.query` with `mode: "agent_routed"`, and the runtime selects the best retrieval lane — semantic search, temporal scan, sparse recall, SDM, graph traversal, or a combination — based on the query and the data it already holds. You don't hard-code the retrieval path; the agent routes itself. ### What this looks like in practice ```python # One call. The runtime picks the lane. response = client.query({ "run_id": "support:agent:ticket-42", "query": "What did the customer say about billing last week?", "mode": "agent_routed", }) ``` Compare with a typical RAG setup where you would write: embed the query → hit a vector store → rank results → truncate to context window → inject into prompt template → call LLM. Each step is a place where retrieval quality can silently degrade, and every step is your code to maintain. ### How this works | Concern | RAG pipeline (you own it) | MuBit agent-routed (runtime owns it) | | --------------------------- | --------------------------- | ------------------------------------------------------- | | Which index to query | Hard-coded per endpoint | Runtime selects lane from query semantics | | Top-k and ranking | Fixed or tuned per use-case | Adaptive per memory compartment | | Context assembly | Your prompt-stuffing logic | Runtime returns ranked results; agent consumes | | Adding a new retrieval mode | New code path + deploy | Already available — runtime routes when useful | | Observability | You instrument each hop | Built-in: lane, latency, and match metadata in response | ### When to override Agent-routed retrieval is the right default. Override it when you have an explicit reason: * **`direct_lane: "semantic_search"`** — force pure vector similarity when you know the answer is a nearest-neighbor match. * **Explicit RAG** — when you need to assemble context from multiple external sources before calling MuBit, or when your compliance rules require auditable retrieval steps. In this case, use `control.ingest` / `control.batch_insert` to load data and `control.query` with a specific `direct_lane` to retrieve it. ### Failure modes and troubleshooting | Symptom | Root cause | Fix | | ------------------------------------ | --------------------------------------------------- | ------------------------------------------------------------------- | | Inconsistent answers across requests | Switching between agent-routed and hard-coded lanes | Pick one default mode and stick with it | | Hard incident triage | Retrieval path not logged | Inspect `lane` and `match_metadata` fields in query response | | Missing deterministic relationships | Relying on similarity for structured facts | Ingest explicit facts and use control filters or graph-aware recall | | Context window overflow | Returning too many results | Set `limit` on query; let the agent prune | ### Next steps * Build retrieval contracts at [Retrieve data](/sdk/retrieve-data). * Review permissions governance at [Data guardrails](/sdk/data-guardrails). import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## Associative Retrieval Use MuBit routed retrieval and context assembly to produce grounded, scoped answers and reusable prompt context. Associative retrieval in MuBit is not just vector similarity. It is the control-plane contract that combines scoped memory selection, evidence ranking, and answer or context synthesis. For most applications, the primary retrieval surfaces are: * `recall()` for answer-oriented retrieval * `getContext()` / `get_context()` for prompt-ready context assembly * raw `control.query` when you need wire-level control ### Mental model * Default to the routed retrieval path. * Keep `run_id` / `session_id` stable. * Use `entry_types` when you need only facts, lessons, or rules. * Use explicit context budgeting when the downstream model window matters. ### Minimal helper-first example ```json title="query_payload.json" { "run_id": "support-thread-42", "query": "What customer preferences are already known?", "mode": "agent_routed", "limit": 5, "entry_types": ["fact", "lesson", "rule"] } ``` ### Failure modes and troubleshooting | Symptom | Root cause | Fix | | -------------------------- | ------------------------------------ | --------------------------------------------------------- | | Sparse evidence | Scope mismatch or weak memory writes | Keep one stable run scope and tag memory intentionally | | Context is too wide | No token budget or type restriction | Use `getContext()` budgeting and `entry_types` | | Retrieval still looks weak | Memory quality degraded | Use `diagnose()` and `memoryHealth()` / `memory_health()` | ### Next steps * Compare retrieval styles at [Associative retrieval vs RAG](/sdk/associative-retrieval-vs-rag). * Implement retrieval patterns at [Retrieve data](/sdk/retrieve-data). import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## Authentication (API key etc) Configure MuBit API-key authentication correctly for secure and stable SDK integrations. MuBit runtime access uses API keys across SDK, HTTP, and gRPC surfaces. Correct key handling is operationally critical for developer teams. Most authentication incidents in AI platform integrations come from key and environment drift, not protocol design. Keep endpoint-key pairing explicit, keep keys server-side only, and run controlled rotation workflows. This gives predictable auth behavior across development, staging, and production. ### API key model * Key format: `mbt___` * Header: `Authorization: Bearer ` Canonical environment setup: ```bash title=".env" MUBIT_API_KEY="mbt___" ``` ### Key lifecycle endpoints | Route | Purpose | | ----------------------------------------------- | --------------------------- | | `POST /v2/core/auth/users` | Create user and initial key | | `POST /v2/core/auth/users/:username/rotate_key` | Rotate user key | | `POST /v2/core/auth/users/:username/revoke_key` | Revoke active key | | `GET /v2/core/auth/users` | List users | | `GET /v2/core/auth/users/:username` | Inspect user | Never expose MuBit API keys in browser clients. ### Failure modes and troubleshooting | Symptom | Root cause | Fix | | ------------------------- | ----------------------------------------- | ------------------------------------------------ | | Invalid key format errors | Truncated or malformed key | Re-issue and validate full `mbt_...` value | | Repeated 401/403 | Wrong key, revoked key, or wrong endpoint | Verify endpoint-key pairing and rotate if needed | | Partial rollout failures | Mixed key versions after rotation | Stage rollout and confirm complete deploy | ### Next steps * Review access guardrails at [Data guardrails](/sdk/data-guardrails). * Review ops posture at [Observability, consistency, and operations](/sdk/observability). import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## Control Plane (State Runtime) Use MuBit control APIs as the stable integration boundary for memory, context assembly, diagnostics, planning state, and coordination. The control plane is the primary public integration boundary for current MuBit. It combines the memory lifecycle, compaction-safe context assembly, diagnostics, learning loop, coordination primitives, and optional explicit planning state. If your team needs one stable runtime boundary for long-running AI systems, standardize on the control plane first. ### Core workflow model | Workflow | Recommended surfaces | | ----------------------- | ----------------------------------------------------------------------------------------- | | Memory write/read | `remember`, `recall`, raw `control.ingest`, raw `control.query` | | Context assembly | `getContext` / `get_context`, `/v2/control/context` | | Learning loop | `reflect`, `recordOutcome` / `record_outcome`, `surfaceStrategies` / `surface_strategies` | | Diagnostics | `memoryHealth` / `memory_health`, `diagnose` | | Coordination | `registerAgent` / `register_agent`, `handoff`, `feedback` | | Explicit planning state | goals, variables, actions, cycles | ### Operating guidance * Use one deterministic `run_id` / `session_id` strategy. * Prefer helper-first SDK methods for application code. * Use `checkpoint` before compaction or risky transitions. * Use `getContext` rather than rebuilding long prompts manually. * Treat explicit state APIs as complementary to memory, not a replacement for it. ### Failure modes and troubleshooting | Symptom | Root cause | Fix | | ------------------------------------------- | --------------------------------------------- | ----------------------------------------------- | | Stale or weak responses | Context was built manually and inconsistently | Standardize on `getContext()` / `get_context()` | | The system does not improve across attempts | Reflection or outcome recording is missing | Pair `reflect()` with `recordOutcome()` | | Multi-agent behavior drifts | Roles are implicit | Register agents and persist handoffs / feedback | ### Next steps * Review the full route contract at [Control HTTP reference](/api-reference/control-http). * Apply the workflow in [Support agent memory loop](/recipes/support-agent-loop). import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## Data Guardrails (ACL related) Protect memory access with route-level policy gates and node-level ACL governance. Data protection in MuBit has two layers: route policy controls external lane access, and ACL controls node-level visibility. If your team treats guardrails as a late-stage concern, failures usually appear first in production as policy denials or unexpected visibility. Define direct-lane policy and ACL ownership early. Then enforce those choices in code paths and operational runbooks. ### Guardrail layer 1: route policy * `/v2/core/health` and `/v2/core/auth/*` are always available. * `/v2/core/search` requires `MUBIT_CORE_ENABLE_DIRECT_SEARCH`. * Other external `/v2/core/*` routes are denied by default middleware. ### Guardrail layer 2: ACL operations | Route | Purpose | | -------------------------- | ----------------------------------- | | `POST /v2/core/acl/grant` | Grant read/write/delete permissions | | `POST /v2/core/acl/revoke` | Remove permissions | | `POST /v2/core/acl/check` | Verify permission state | ### Failure modes and troubleshooting | Symptom | Root cause | Fix | | --------------------- | ------------------------- | ----------------------------------------------------- | | Direct method denied | Lane policy off | Use routed control query or enable lane intentionally | | Unexpected visibility | ACL model incomplete | Validate grant/revoke/check flow | | Broad blast radius | Policies enabled globally | Roll out lane flags per environment | ### Next steps * See policy details at [Core direct lanes policy](/api-reference/core-direct-lanes). * See authentication setup at [Authentication](/sdk/authentication). import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## Data Plane (Memory) Understand direct-core memory operations, policy boundaries, and when low-level lanes are appropriate. The MuBit data plane exposes low-level memory primitives. It is powerful and intentionally policy-constrained. Use direct-core methods only when you need an explicitly enabled compatibility lane such as direct semantic search. For most app request paths, control-plane methods are safer and easier to operate. Keeping this boundary clear reduces policy and reliability incidents. ### Core route families | Family | Representative routes | | ---------------- | ------------------------------------ | | Direct retrieval | `/v2/core/search` | | Health/auth | `/v2/core/health`, `/v2/core/auth/*` | ### Policy behavior * `/v2/core/search` requires `MUBIT_CORE_ENABLE_DIRECT_SEARCH`. * Other external `/v2/core/*` routes should be treated as compatibility-only or policy-denied, not normal SDK adoption paths. ### Failure modes and troubleshooting | Symptom | Root cause | Fix | | ---------------------------------------- | -------------------------- | -------------------------------------------- | | Core calls are denied | Direct lanes disabled | Use control path or enable lane deliberately | | Retrieval behavior diverges unexpectedly | Mixed control/core usage | Define one default retrieval path | | Operational overhead increases | Broad direct-core adoption | Restrict direct-core to targeted features | ### Next steps * See direct-lane policy contract at [Core direct lanes policy](/api-reference/core-direct-lanes). * See how the runtime fits together at [How MuBit works](/sdk/how-mubit-works). import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## Framework Integrations Native MuBit memory adapters for popular agent frameworks. This page is a reference index; full walkthroughs live on the per-framework pages. MuBit ships native adapters for popular agent frameworks. Each adapter plugs into the framework's own memory or store interface, so agents pick up persistent semantic memory and cross-session learning without restructuring how they're built. Pick a framework, copy the minimal snippet, then jump to the full walkthrough on the framework's page. ### At a glance | Framework | Language | Adapter pattern | Install | | ----------------- | -------- | ----------------------------------- | -------------------------------------------- | | **CrewAI** | Python | `StorageBackend` for unified Memory | `pip install mubit-crewai[crewai]` | | **LangGraph** | Python | `BaseStore` with batch ops | `pip install mubit-langgraph[langgraph]` | | **LangGraph** | JS | `BaseStore` with async ops | `npm install @mubit-ai/langgraph` | | **LangChain** | Python | `BaseMemory` / `MubitChatMemory` | `pip install mubit-langchain[langchain]` | | **LlamaIndex** | Python | `BaseChatStore` + `VectorStore` | `pip install mubit-llama-index[llama-index]` | | **Google ADK** | Python | `BaseMemoryService` for Runner | `pip install mubit-adk[adk]` | | **Agno** | Python | `MemoryDb` + `Toolkit` | `pip install mubit-agno[agno]` | | **Vercel AI SDK** | JS | `wrapLanguageModel()` middleware | `npm install @mubit-ai/ai-sdk` | | **MCP** | Any | 10 tools over stdio transport | `npm install @mubit-ai/mcp` | All adapters use the canonical MuBit SDK transport internally. Every Python adapter exposes the full MAS surface — see [Feature parity matrix](#feature-parity-matrix). **Version compatibility:** these adapters require `mubit-sdk >= 0.6.0` and ship at `0.5.1+` on PyPI. Always upgrade integration packages alongside the core SDK. *** ### CrewAI Routes CrewAI's unified Memory system through MuBit. Agent observations persist across runs and are queryable across crews. ```bash pip install mubit-crewai[crewai] ``` ```python title="crewai_minimal.py" from mubit_crewai import MubitCrewMemory from crewai import Crew, Process memory = MubitCrewMemory(api_key="mbt_...", session_id="crew-run-1") crew = Crew( agents=[researcher, writer], tasks=[research_task, write_task], process=Process.sequential, memory=memory.as_crew_memory(), ) crew.kickoff(inputs={"topic": "AI safety"}) ``` Full walkthrough → [/sdk/integrations/crewai](/sdk/integrations/crewai) *** ### LangGraph Use MuBit as a persistent store for LangGraph `StateGraph` nodes. Each node reads or writes through `PutOp` / `SearchOp`. ```bash pip install mubit-langgraph[langgraph] ``` ```python title="langgraph_minimal.py" from mubit_langgraph import MubitStore from langgraph.store.base import PutOp, SearchOp store = MubitStore(api_key="mbt_...") NS = ("memories", "user-1", "session-1") store.batch([PutOp(namespace=NS, key="finding-1", value={"text": "...", "intent": "lesson"})]) results = store.batch([SearchOp(namespace_prefix=NS, query="security issues", limit=5)]) graph.compile(store=store) ``` Full walkthrough → [/sdk/integrations/langgraph](/sdk/integrations/langgraph) *** ### LangChain Drop-in `BaseMemory` for any LangChain chain. Loads context before each call and saves interactions after; cross-session memory is automatic via MuBit's semantic retrieval. ```bash pip install mubit-langchain[langchain] ``` ```python title="langchain_minimal.py" from mubit_langchain import MubitChatMemory from langchain_openai import ChatOpenAI memory = MubitChatMemory(api_key="mbt_...", session_id="chat-1") llm = ChatOpenAI(model="gpt-4o-mini") context = memory.load_memory_variables({"input": "What happened yesterday?"}) # build messages with context["history"], call LLM memory.save_context({"input": question}, {"output": response}) ``` Full walkthrough → [/sdk/integrations/langchain](/sdk/integrations/langchain) *** ### LlamaIndex Two surfaces: `MubitChatStore` (implements `BaseChatStore` for conversation history) and `MubitVectorStore` (implements `VectorStore` for retrieved documents). ```bash pip install mubit-llama-index[llama-index] ``` ```python title="llamaindex_minimal.py" from mubit_llama_index import MubitChatStore, MubitVectorStore from llama_index.core import VectorStoreIndex, StorageContext from llama_index.core.memory import ChatMemoryBuffer chat_store = MubitChatStore(api_key="mbt_...", session_id="session-1") vector_store = MubitVectorStore(api_key="mbt_...", session_id="session-1") memory = ChatMemoryBuffer.from_defaults(chat_store=chat_store, chat_store_key="user-1") index = VectorStoreIndex.from_documents( documents, storage_context=StorageContext.from_defaults(vector_store=vector_store), ) ``` Full walkthrough → [/sdk/integrations/llamaindex](/sdk/integrations/llamaindex) *** ### Google ADK Plug MuBit into ADK's `Runner` as a `BaseMemoryService`. Session events are auto-ingested; memory search enriches agent context on the next turn. ```bash pip install mubit-adk[adk] ``` ```python title="google_adk_minimal.py" from mubit_adk import MubitMemoryService from google.adk.runners import Runner from google.adk.sessions import InMemorySessionService memory = MubitMemoryService(api_key="mbt_...") runner = Runner( agent=root_agent, app_name="travel", session_service=InMemorySessionService(), memory_service=memory, ) ``` Full walkthrough → [/sdk/integrations/google-adk](/sdk/integrations/google-adk) *** ### Agno Two integration surfaces: `MemoryDb` for Agno's built-in memory system, and a `Toolkit` with LLM-callable memory tools. ```bash pip install mubit-agno[agno] ``` ```python title="agno_minimal.py" from agno.agent import Agent from agno.memory.v2.memory import Memory from mubit_agno import MubitAgnoMemory mubit = MubitAgnoMemory(api_key="mbt_...", session_id="run-1") agent = Agent( name="Assistant", memory=Memory(db=mubit.as_memory_db()), tools=[mubit.as_toolkit()], enable_agentic_memory=True, ) agent.run("What do we know about the production database?") ``` Full walkthrough → [/sdk/integrations/agno](/sdk/integrations/agno) *** ### Vercel AI SDK Middleware that wraps any AI SDK model with automatic memory injection and interaction capture. ```bash npm install @mubit-ai/ai-sdk ``` ```js title="vercel_ai_sdk_minimal.mjs" import { wrapLanguageModel, generateText } from "ai"; import { openai } from "@ai-sdk/openai"; import { mubitMemoryMiddleware } from "@mubit-ai/ai-sdk"; const model = wrapLanguageModel({ model: openai("gpt-4o"), middleware: mubitMemoryMiddleware({ apiKey: process.env.MUBIT_API_KEY, sessionId: "session-1", agentId: "support-agent", }), }); await generateText({ model, prompt: "How do I reset my password?" }); ``` Full walkthrough → [/sdk/integrations/vercel-ai-sdk](/sdk/integrations/vercel-ai-sdk) *** ### MCP Exposes MuBit as 10 tools over MCP stdio transport. Usable by any MCP-compatible agent (Claude Desktop, Cursor, custom agents). ```bash npm install @mubit-ai/mcp ``` ```bash title="mcp_minimal.sh" npx @mubit-ai/mcp --api-key $MUBIT_API_KEY --endpoint https://api.mubit.ai ``` Tools available: `mubit_remember`, `mubit_recall`, `mubit_context`, `mubit_archive`, `mubit_dereference`, `mubit_reflect`, `mubit_lessons`, `mubit_checkpoint`, `mubit_outcome`, `mubit_strategies`, `mubit_register_agent`, `mubit_handoff`, `mubit_feedback`, `mubit_diagnose`, `mubit_memory_health`. Full walkthrough → [/sdk/integrations/mcp](/sdk/integrations/mcp) *** ### Common MAS extensions Every adapter exposes these MuBit-specific methods beyond the base framework interface: | Method | Purpose | | ---------------------- | ------------------------------------------------------------- | | `checkpoint()` | Save a snapshot of memory state | | `record_outcome()` | Record success / failure with an RL-style signal | | `surface_strategies()` | Cluster recurring lessons into reusable strategy descriptors | | `register_agent()` | Register an agent with role, scopes, capabilities | | `handoff()` | Transfer control between agents (requires `task_id`) | | `feedback()` | Submit a verdict on a handoff | | `diagnose()` | Surface failure-path lessons for debugging | | `get_context()` | Fetch a token-budgeted context block | | `reflect()` | Extract lessons from session evidence | | `lessons()` | List lessons with optional filtering | | `archive()` | Store an exact reusable artifact with a stable `reference_id` | | `dereference()` | Fetch exact content by `reference_id` | ### Feature parity matrix All Python adapters expose the full MuBit MAS surface through the shared `mubit-integration-base` client. JS/TS adapters expose the same surface in `camelCase`. | Method | CrewAI | LangGraph (Py) | LangChain | LlamaIndex | Google ADK | Agno | Vercel AI | MCP | | -------------------------------- | :----: | :------------: | :-------: | :--------: | :--------: | :--: | :----------: | :------------------: | | `remember` / `ingest` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ auto | ✅ `mubit_remember` | | `recall` / `query` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ auto | ✅ `mubit_recall` | | `get_context` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ `mubit_context` | | `checkpoint` | ✅ | ✅ | ✅ | via client | ✅ | ✅ | via client | ✅ `mubit_checkpoint` | | `record_outcome` | ✅ | ✅ | ✅ | via client | ✅ | ✅ | via client | ✅ `mubit_outcome` | | `surface_strategies` | ✅ | ✅ | ✅ | via client | ✅ | ✅ | via client | ✅ `mubit_strategies` | | `register_agent` / `list_agents` | ✅ | ✅ | ✅ | via client | ✅ | ✅ | via client | ✅ | | `handoff` / `feedback` | ✅ | ✅ | ✅ | via client | ✅ | ✅ | via client | ✅ | | `diagnose` | ✅ | ✅ | ✅ | via client | ✅ | ✅ | via client | ✅ `mubit_diagnose` | | `reflect` | ✅ | ✅ | ✅ | via client | ✅ | ✅ | ✅ on run end | ✅ `mubit_reflect` | | `archive` / `dereference` | ✅ | ✅ | ✅ | via client | ✅ | ✅ | via client | ✅ | **"via client"** means the adapter exposes the method on its underlying `MubitControlClient` (e.g. `chat_store.client.checkpoint(...)` on LlamaIndex, `mubit.client.diagnose(...)` on Vercel AI SDK). The framework-facing surface stays focused on the memory contract the framework expects, while the full MAS coordination remains available in the same process. import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## Full Fledged Support Agent Build an end-to-end support agent with helper-first memory, compaction-safe context, coordination, and reinforcement. This page shows one complete support-agent shape per SDK using the **current recommended MuBit flow**: 1. register the agents involved 2. store facts, traces, and preferences 3. assemble context before responding 4. checkpoint before compaction or risky transitions 5. coordinate specialist review with handoff and feedback 6. reflect and record outcomes so future cases improve ### Environment ```bash title=".env" MUBIT_API_KEY="mbt___" MUBIT_ENDPOINT="https://api.mubit.ai" ``` ### Full implementation ```python import os from mubit import Client RUN_ID = "support:acme:ticket-42" client = Client( endpoint=os.getenv("MUBIT_ENDPOINT", "https://api.mubit.ai"), api_key=os.environ["MUBIT_API_KEY"], run_id=RUN_ID, transport="http", ) client.register_agent(session_id=RUN_ID, agent_id="support", role="support") client.register_agent(session_id=RUN_ID, agent_id="billing", role="billing") client.remember( session_id=RUN_ID, agent_id="support", content="Customer Taylor wants concise Friday updates and is asking about invoice INV-7781.", intent="fact", metadata={"customer": "taylor", "ticket": 42}, ) client.remember( session_id=RUN_ID, agent_id="billing", content="Invoice INV-7781 has a stale tax table mismatch on line item 4.", intent="trace", metadata={"invoice": "INV-7781", "source": "billing-review"}, ) context = client.get_context( session_id=RUN_ID, query="Draft the next customer-safe update with the billing root cause and next step.", mode="full", max_token_budget=800, ) checkpoint = client.checkpoint( session_id=RUN_ID, agent_id="support", label="pre-response-compaction", context_snapshot=context.get("context_block", ""), ) handoff = client.handoff( session_id=RUN_ID, task_id="ticket-42-billing-review", from_agent_id="support", to_agent_id="billing", content="Confirm the billing root cause for invoice INV-7781 before we reply.", requested_action="review", ) feedback = client.feedback( session_id=RUN_ID, handoff_id=handoff["handoff_id"], from_agent_id="billing", verdict="approve", comments="Root cause confirmed: stale tax table mismatch on line item 4.", ) reflection = client.reflect(session_id=RUN_ID) # Use the lesson id returned inline on the reflect response — avoids racing # with the vector-store index when listing immediately after a write. lesson_id = next( (l.get("lesson_id") for l in (reflection.get("lessons") or []) if l.get("lesson_id")), None, ) if lesson_id: client.record_outcome( session_id=RUN_ID, agent_id="support", reference_id=lesson_id, outcome="success", signal=0.75, rationale="The support response used the stored customer preference and the validated billing root cause.", ) strategies = client.surface_strategies( session_id=RUN_ID, lesson_types=["success", "failure"], max_strategies=5, ) print({ "checkpoint_id": checkpoint.get("checkpoint_id"), "handoff_id": handoff.get("handoff_id"), "feedback_id": feedback.get("feedback_id"), "lessons_stored": reflection.get("lessons_stored"), "strategy_count": len(strategies.get("strategies", [])), }) ``` ```ts import { Client } from "@mubit-ai/sdk"; const RUN_ID = "support:acme:ticket-42"; const client = new Client({ endpoint: process.env.MUBIT_ENDPOINT ?? "https://api.mubit.ai", api_key: process.env.MUBIT_API_KEY, run_id: RUN_ID, transport: "http", }); await client.registerAgent({ session_id: RUN_ID, agent_id: "support", role: "support" }); await client.registerAgent({ session_id: RUN_ID, agent_id: "billing", role: "billing" }); await client.remember({ session_id: RUN_ID, agent_id: "support", content: "Customer Taylor wants concise Friday updates and is asking about invoice INV-7781.", intent: "fact", metadata: { customer: "taylor", ticket: 42 }, }); await client.remember({ session_id: RUN_ID, agent_id: "billing", content: "Invoice INV-7781 has a stale tax table mismatch on line item 4.", intent: "trace", metadata: { invoice: "INV-7781", source: "billing-review" }, }); const context = await client.getContext({ session_id: RUN_ID, query: "Draft the next customer-safe update with the billing root cause and next step.", mode: "full", max_token_budget: 800, }); const checkpoint = await client.checkpoint({ session_id: RUN_ID, agent_id: "support", label: "pre-response-compaction", context_snapshot: context.context_block ?? "", }); const handoff = await client.handoff({ session_id: RUN_ID, task_id: "ticket-42-billing-review", from_agent_id: "support", to_agent_id: "billing", content: "Confirm the billing root cause for invoice INV-7781 before we reply.", requested_action: "review", }); const feedback = await client.feedback({ session_id: RUN_ID, handoff_id: handoff.handoff_id, from_agent_id: "billing", verdict: "approve", comments: "Root cause confirmed: stale tax table mismatch on line item 4.", }); const reflection = await client.reflect({ session_id: RUN_ID }); // lesson_id is populated inline on each ReflectLesson so callers don't need // a round-trip through client.lessons() which can race with index // propagation immediately after a write. const lessonId = reflection.lessons?.find((l) => l.lesson_id)?.lesson_id; if (lessonId) { await client.recordOutcome({ session_id: RUN_ID, agent_id: "support", reference_id: lessonId, outcome: "success", signal: 0.75, rationale: "The support response used the stored customer preference and the validated billing root cause.", }); } const strategies = await client.surfaceStrategies({ session_id: RUN_ID, lesson_types: ["success", "failure"], max_strategies: 5, }); console.log({ checkpoint_id: checkpoint.checkpoint_id, handoff_id: handoff.handoff_id, feedback_id: feedback.feedback_id, lessons_stored: reflection.lessons_stored, strategy_count: strategies.strategies?.length ?? 0, }); ``` ```rust use mubit_sdk::{ CheckpointOptions, Client, ClientConfig, FeedbackOptions, GetContextOptions, HandoffOptions, RecordOutcomeOptions, ReflectOptions, RegisterAgentOptions, RememberOptions, SurfaceStrategiesOptions, }; use serde_json::json; use std::env; let run_id = "support:acme:ticket-42".to_string(); let config = ClientConfig::new("https://api.mubit.ai") .api_key(env::var("MUBIT_API_KEY")?) .run_id(&run_id); let client = Client::connect(config).await?; let mut support = RegisterAgentOptions::new("support"); support.run_id = Some(run_id.clone()); support.role = "support".to_string(); client.register_agent(support).await?; let mut billing = RegisterAgentOptions::new("billing"); billing.run_id = Some(run_id.clone()); billing.role = "billing".to_string(); client.register_agent(billing).await?; let mut support_fact = RememberOptions::new( "Customer Taylor wants concise Friday updates and is asking about invoice INV-7781.", ); support_fact.run_id = Some(run_id.clone()); support_fact.agent_id = Some("support".to_string()); support_fact.intent = Some("fact".to_string()); support_fact.metadata = Some(json!({"customer":"taylor","ticket":42})); client.remember(support_fact).await?; let mut billing_trace = RememberOptions::new( "Invoice INV-7781 has a stale tax table mismatch on line item 4.", ); billing_trace.run_id = Some(run_id.clone()); billing_trace.agent_id = Some("billing".to_string()); billing_trace.intent = Some("trace".to_string()); billing_trace.metadata = Some(json!({"invoice":"INV-7781","source":"billing-review"})); client.remember(billing_trace).await?; let mut context = GetContextOptions::default(); context.run_id = Some(run_id.clone()); context.query = Some("Draft the next customer-safe update with the billing root cause and next step.".to_string()); context.mode = Some("full".to_string()); context.max_token_budget = Some(800); let context = client.get_context(context).await?; let mut checkpoint = CheckpointOptions::new( "pre-response-compaction", context.get("context_block").and_then(|v| v.as_str()).unwrap_or(""), ); checkpoint.run_id = Some(run_id.clone()); checkpoint.agent_id = Some("support".to_string()); let checkpoint = client.checkpoint(checkpoint).await?; let mut handoff = HandoffOptions::new( "ticket-42-billing-review", "support", "billing", "Confirm the billing root cause for invoice INV-7781 before we reply.", ); handoff.run_id = Some(run_id.clone()); handoff.requested_action = "review".to_string(); let handoff = client.handoff(handoff).await?; let handoff_id = handoff.get("handoff_id").and_then(|v| v.as_str()).unwrap().to_string(); let mut feedback = FeedbackOptions::new(handoff_id.clone(), "approve"); feedback.run_id = Some(run_id.clone()); feedback.from_agent_id = Some("billing".to_string()); feedback.comments = "Root cause confirmed: stale tax table mismatch on line item 4.".to_string(); let feedback = client.feedback(feedback).await?; let mut reflect = ReflectOptions::default(); reflect.run_id = Some(run_id.clone()); let reflection = client.reflect(reflect).await?; let lessons = client.list_lessons(json!({"run_id": run_id, "limit": 20})).await?; if let Some(lesson_id) = lessons["lessons"][0]["id"].as_str() { let mut outcome = RecordOutcomeOptions::new(lesson_id, "success"); outcome.run_id = Some(run_id.clone()); outcome.agent_id = Some("support".to_string()); outcome.signal = 0.75; outcome.rationale = "The support response used the stored customer preference and the validated billing root cause.".to_string(); client.record_outcome(outcome).await?; } let mut strategies = SurfaceStrategiesOptions::default(); strategies.run_id = Some(run_id.clone()); strategies.lesson_types = vec!["success".to_string(), "failure".to_string()]; strategies.max_strategies = 5; let strategies = client.surface_strategies(strategies).await?; println!("{} {} {} {} {}", checkpoint["checkpoint_id"], handoff["handoff_id"], feedback["feedback_id"], reflection["lessons_stored"], strategies["strategies"] ); ``` ### Operational notes * Start helper-first and drop to raw `client.*` only where helper coverage is intentionally lower, such as explicit lesson listing. * `mubit.auto` is the public zero-friction path if you want Python LLM traces to feed this same memory loop automatically. ### Next steps * Read the route contract at [Control HTTP reference](/api-reference/control-http). * Use the narrower examples at [Support agent memory loop](/recipes/support-agent-loop) and [Multi-agent shared state](/recipes/multi-agent-shared-state). import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## gRPC Transport Guide When to use gRPC vs HTTP, endpoint configuration, TLS, and proto file locations for MuBit SDKs. MuBit supports both HTTP/REST and gRPC transports. All SDK methods work identically regardless of transport — the choice affects performance characteristics, not API surface. ### When to use gRPC vs HTTP | Factor | gRPC | HTTP | | ------------------- | --------------------------------------------------------------------------- | ------------------------------------------ | | **Latency** | Lower per-call overhead (binary protocol, persistent connections) | Slightly higher due to JSON serialization | | **Streaming** | Native bidirectional streaming (`Subscribe`, `StreamSearch`, `WatchMemory`) | SSE for events, no bidirectional streaming | | **Debugging** | Requires gRPC tooling (grpcurl, Bloom RPC) | curl, browser dev tools | | **Proxies/CDNs** | Requires HTTP/2-aware infrastructure | Works everywhere | | **Browser clients** | Requires grpc-web proxy | Native fetch/XMLHttpRequest | **Recommendation:** Use gRPC for backend services where you want lower latency and streaming. Use HTTP for browser clients, serverless functions, or environments with HTTP/1.1-only proxies. ### Configuration #### Environment variables ```bash title=".env" MUBIT_TRANSPORT="grpc" MUBIT_GRPC_ENDPOINT="grpc.api.mubit.ai:443" ``` #### SDK constructor ```python from mubit import Client client = Client( api_key="mbt_...", transport="grpc", # endpoint defaults to grpc.api.mubit.ai:443 ) ``` ```js import { Client } from "@mubit-ai/sdk"; const client = new Client({ api_key: "mbt_...", transport: "grpc", // endpoint defaults to grpc.api.mubit.ai:443 }); ``` ```rust use mubit_sdk::{Client, ClientConfig}; let config = ClientConfig::new("grpc.api.mubit.ai:443") .api_key("mbt_...") .transport("grpc"); let client = Client::connect(config).await?; ``` ### Endpoints | Environment | gRPC endpoint | TLS | | ------------------- | --------------------------- | --------------- | | Hosted (production) | `grpc.api.mubit.ai:443` | Yes (system CA) | | Hosted (dev) | `grpc.api.dev.mubit.ai:443` | Yes (system CA) | | Local | `127.0.0.1:50051` | No (plaintext) | TLS is handled automatically by the SDK — no certificate configuration is needed for hosted endpoints. ### Proto file locations The proto definitions are available in the repository: | Proto file | Service | Description | | ------------------------------ | ---------------- | --------------------------------------------------------------------------- | | `proto/mubit/v1/core.proto` | `MubitService` | Core data-plane operations (insert, search, memory, sessions) | | `proto/mubit/v1/control.proto` | `ControlService` | Control-plane operations (ingest, query, context, reflection, coordination) | These can be used with any gRPC code generator if you need a client outside the official SDKs. ### Streaming RPCs gRPC enables streaming RPCs not available over HTTP: | RPC | Direction | Purpose | | --------------------- | --------------- | ---------------------------------------- | | `Subscribe` (Control) | Server → Client | Stream control-plane events in real-time | | `StreamSearch` (Core) | Server → Client | Stream search results as they're found | | `WatchMemory` (Core) | Server → Client | Watch for scratchpad memory changes | ```python # Stream control events async for event in client.subscribe(run_id="my-run"): print(f"Event: {event.type} — {event.data}") ``` ```js // Stream control events for await (const event of client.subscribe({ runId: "my-run" })) { console.log(`Event: ${event.type} — ${event.data}`); } ``` ```rust // Stream control events let mut stream = client.control().subscribe("my-run").await?; while let Some(event) = stream.next().await { println!("Event: {} — {}", event.r#type, event.data); } ``` ### Auto transport When `transport` is set to `"auto"` (the default), the SDK: 1. Checks if a gRPC connection can be established to the endpoint 2. Falls back to HTTP if gRPC is unavailable 3. Caches the result for subsequent calls This works well for most deployments. Set an explicit transport when you need deterministic behavior or want to avoid the initial probe. ### Troubleshooting | Symptom | Cause | Fix | | -------------------------------- | ----------------------------------------- | -------------------------------------------------------------- | | Connection refused on port 50051 | gRPC server not running or wrong endpoint | Verify endpoint; for local, ensure `make run-mubit` is running | | TLS handshake failure | Self-signed cert or wrong CA | For hosted endpoints, ensure system CA store is current | | `UNIMPLEMENTED` error | Proto version mismatch | Update SDK to latest version | | `DEADLINE_EXCEEDED` | Network timeout | Check connectivity; increase timeout in client config | | Fields missing in response | `keepCase` not set (JS SDK \< v0.4.1) | Update `@mubit-ai/sdk` to >= v0.4.1 | ### Next steps * See [SDK Configuration Reference](#TODO) for all config options. * See [Control gRPC reference](/api-reference/control-grpc) for the full RPC surface. import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## How MuBit works How your agents go from stateless to self-improving with MuBit's memory loop. MuBit is a memory engine for AI agents. It stores what your agents learn, retrieves the right context before each LLM call, and extracts reusable lessons so agents improve over time. Everything runs behind a single control-plane boundary — your agent talks to MuBit, MuBit talks to storage. ### Overview MuBit architecture: Agent writes memory with remember(), retrieves context with recall()/getContext(), sends enriched prompt to LLM, then feeds outcomes back through recordOutcome() and reflect() to improve over time. The core loop has four steps: Your agent calls `remember()` to store facts, traces, or observations as they happen. Before the next LLM call, `recall()` finds relevant evidence and `getContext()` assembles a token-budgeted context block with rules, lessons, and facts. After a run completes, `reflect()` extracts reusable lessons from what happened. Recurring lessons get promoted from run-scoped to session-scoped to global. `recordOutcome()` feeds success/failure signals back into the lesson store, strengthening what worked and weakening what didn't. Over multiple runs, agents accumulate lessons that make them better at the task — without retraining the LLM. ### Component model MuBit exposes three API surfaces. Most application logic belongs on the control plane. | Surface | Responsibility | Typical use | | --------- | ------------------------------------------------------------------------------------------------ | -------------------------------- | | `control` | Memory lifecycle, context assembly, diagnostics, learning loop, coordination, and planning state | Default application path | | `core` | Direct search, sessions, scratch memory, and specialized low-level primitives | Advanced or specialized features | | `auth` | User and API key lifecycle | Admin and provisioning workflows | * Start with helper-first control usage: `remember`, `recall`, `getContext`, `checkpoint`, `reflect`, `recordOutcome`. * Introduce `core.*` only when you need direct-lane or branch/session primitives. * Keep `auth.*` out of end-user request handlers. ### Execution contract 1. Write memory with `remember()` or raw `control.ingest`. 2. If you use raw ingest, poll `control.get_ingest_job` until `done=true` before freshness-critical reads. 3. Read with `recall()` or assemble context with `getContext()` / `get_context()`. 4. Checkpoint before compaction. 5. Reflect and record outcomes when the attempt finishes. ### Query and context controls that matter | Control | Why it matters | | ---------------------------- | --------------------------------------------------------------------- | | `run_id` / `session_id` | Defines the memory scope | | `mode` on context | Chooses full, summary, or sectioned context assembly | | `max_token_budget` | Keeps context within the active model budget | | `entry_types` | Restricts retrieval to facts, lessons, rules, traces, and other types | | `diagnose` / `memory_health` | Explains weak retrieval and low-quality memory | ### Minimal helper-first example ```python title="helper_flow.py" client.remember( session_id="support:acme:ticket-42", agent_id="support-agent", content="Customer Taylor prefers concise Friday updates.", intent="fact", ) answer = client.recall( session_id="support:acme:ticket-42", query="What preference do we already know for Taylor?", ) context = client.get_context( session_id="support:acme:ticket-42", query="Draft the next response.", mode="summary", max_token_budget=300, ) ``` ### Failure modes and troubleshooting | Symptom | Root cause | Fix | | ------------------------------------------- | ---------------------------------------------------------- | ----------------------------------------------------------------------- | | Recent write missing from later context | Raw ingest completion was ignored | Gate reads on `done=true` or use `remember()` | | Context is too long or too noisy | No explicit context mode or budget | Use `getContext()` / `get_context()` with `mode` and `max_token_budget` | | The system does not improve across attempts | Reflection or outcomes are missing | Pair `reflect()` with `recordOutcome()` | | Security boundary drift | `auth` or direct `core` calls leaked into the request path | Keep application logic on the control plane | ### Deep dives * [Data-plane memory component](/sdk/data-plane-memory) * [Control-plane state runtime component](/sdk/control-plane) ### Next steps * Extend the same project with [Add data](/sdk/add-data). * Extend retrieval behavior with [Retrieve data](/sdk/retrieve-data). import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## LLM Provider Support Which LLM client libraries mubit.learn auto-instruments, and what each integration captures. `mubit.learn.init()` patches your existing LLM client classes in place — no wrapper code required. The table below lists the libraries that are auto-instrumented today. | Provider | Python | Node.js | | ----------------- | --------------------- | ---------------------------------------- | | **OpenAI** | `openai` | `openai` | | **Anthropic** | `anthropic` | `@anthropic-ai/sdk` | | **Google Gemini** | `google-generativeai` | `@google/generative-ai` | | **LiteLLM** | `litellm` | — | | **Vercel AI SDK** | — | `ai` (via `@mubit-ai/ai-sdk` middleware) | What gets patched is concrete: the constructor of the provider's client class. Subsequent calls to `.messages.create()`, `.chat.completions.create()`, etc. flow through MuBit before and after the network call. ### What auto-instrumentation captures For every wrapped call: * **Pre-call:** relevant lessons retrieved from MuBit and injected into the system message (or as a synthetic system message if none exists). * **The call itself:** sent to the provider unchanged. * **Post-call:** the request, response, model name, latency, and token usage are recorded as a `trace` in the active session. On session end (or `auto_reflect=True`), `reflect()` runs and extracts new lessons from the session's traces. ### Verifying instrumentation ```python import anthropic, mubit.learn mubit.learn.init(api_key="mbt_...", agent_id="my-agent") client = anthropic.Anthropic() print(client._mubit_learn_wrapped) # True ``` A wrapped client carries `_mubit_learn_wrapped = True`. If `False`, the patch didn't take — usually because `mubit.learn.init()` was called *after* the client was constructed. Patch first, instantiate second. ### Opting individual calls out ```python from mubit.learn import bypass with bypass(): resp = anthropic.Anthropic().messages.create(...) # not captured ``` Useful for system-utility calls (cost estimates, eval pipelines, retries you don't want recorded as evidence). ### Provider-specific notes #### OpenAI * Both sync (`OpenAI`) and async (`AsyncOpenAI`) clients are patched. * Streaming responses are captured incrementally; the full assembled output is recorded. * Tool/function call traces include the tool name and arguments. #### Anthropic * Both sync (`Anthropic`) and async (`AsyncAnthropic`) clients are patched. * `messages.create` is wrapped; the deprecated `completions.create` is not. * Tool-use blocks are captured as part of the response trace. #### Google Gemini * The `google-generativeai` client is patched on first import after `init()`. * Multi-turn `ChatSession` instances inherit instrumentation from their parent client. #### LiteLLM (Python only) * LiteLLM's unified `completion()` and `acompletion()` calls are patched. * The provider-specific client behind LiteLLM is not double-wrapped. #### Vercel AI SDK (Node only) * Use `mubitMemoryMiddleware` from `@mubit-ai/ai-sdk` with `wrapLanguageModel()` rather than `learn.init()`. The Node `learn` API auto-instruments the lower-level provider SDKs but not the AI SDK abstraction. * Full example: see [Framework integrations → Vercel AI SDK](/sdk/integrations/vercel-ai-sdk). ### Adding a provider Open an issue or a PR against `mubit-sdk` with the client class name and the call surface that should be wrapped (constructor + the methods that hit the network). Most providers take less than 50 lines of patch code; the contract is documented in `mubit/learn/_patches/README.md`. import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## Observability, Consistency, and Operations Operate MuBit safely with ingest/query/context telemetry, memory diagnostics, checkpoint discipline, and explicit agent coordination traces. Production memory systems need more than good demos. MuBit gives you explicit lifecycle boundaries and diagnostics so you can tell whether the system is learning, compacting safely, and retrieving the right evidence. ### Operational checklist | Area | Track this | | ----------------- | ------------------------------------------------------------------ | | Freshness | Ingest accepted-to-done latency | | Retrieval | Query and context latency, weak-evidence rates | | Learning loop | Reflection volume, outcome recording coverage, surfaced strategies | | Memory quality | `memory_health` results, contradictions, stale entries | | Compaction safety | Checkpoint cadence and checkpoint failures | | Coordination | Handoff and feedback visibility across agents | ### Consistency model * Keep deterministic `run_id` / `session_id` mapping across writes and reads. * Use `getContext` rather than reconstructing large prompts manually. * Treat checkpoints as explicit lifecycle boundaries. * Use `diagnose` and `memory_health` before changing retrieval prompts or weights. ### LLM telemetry MuBit tracks all internal LLM calls (ingestion routing, query synthesis, reflection, snapshots) with Prometheus metrics available at the `/metrics` endpoint. | Metric | Labels | Description | | --------------------------------- | ------------------------------ | ------------------------------------------- | | `mubit_llm_calls_total` | task, provider, model, success | Total LLM call count by task and outcome | | `mubit_llm_call_duration_seconds` | task, provider | Call latency histogram (buckets: 0.1s–30s) | | `mubit_llm_tokens_total` | task, provider, token\_type | Token consumption (prompt vs completion) | | `mubit_llm_retries_total` | task, provider | Retry attempts due to rate limits or errors | | `mubit_agent_degraded_total` | agent, reason | Agent fallbacks to heuristic mode | Storage health is also tracked: | Metric | Description | | ---------------------------------- | ------------------------------------------- | | `mubit_storage_compaction_pending` | Pending compaction work (0 = healthy) | | `mubit_storage_write_stall` | Whether writes are being throttled (0 or 1) | | `mubit_disk_total_bytes` | Total disk capacity | | `mubit_disk_used_bytes` | Disk space used | | `mubit_disk_usage_pct` | Disk usage percentage | These metrics can be scraped by Prometheus at a 15-second interval. The LLM Activity page in the user console provides a dashboard view. ### Failure modes and troubleshooting | Symptom | Root cause | Fix | | ------------------------------------------- | ------------------------------------------------- | ------------------------------------------------------ | | Learning appears inactive | Reflections exist but outcomes are never recorded | Record outcomes against reflected lessons/rules | | Important details disappear after long runs | No checkpoint before compaction | Save checkpoints before summarization or window resets | | Debugging memory quality is slow | No memory diagnostics in the workflow | Add `memory_health` and `diagnose` to incident review | ### Next steps * Review route-level contracts at [Control HTTP reference](/api-reference/control-http). * Apply the learning loop at [Support agent memory loop](/recipes/support-agent-loop). import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## Projects, Agents, Skills, Prompts The Managed MuBit resource model — group agents into Projects, version their prompts and skills, and ship changes safely with a candidate → active promotion flow. On a **Managed MuBit** deployment (MuBit-hosted or self-hosted with the control plane), agents aren't just transient run-time identities — they're **first-class resources**. A Project groups related agents, each agent owns a versioned system prompt and a set of skills, and everything can be inspected, rolled out, and rolled back without touching your deployed SDK code. This page is the SDK view of that resource model. Every operation below is also available point-and-click in the **Mubit Console** at [console.mubit.ai](https://console.mubit.ai) — inline `In the console` notes under each section below show the exact route and buttons. For the raw wire contract see [Control HTTP](/api-reference/control-http#managed-resources-projects-agents-skills-prompts) and [Control gRPC](/api-reference/control-grpc#managed-resources-projects-agents-skills-prompts). ### The hierarchy ``` Project ├── Agent Card (name, role, description, active system prompt) │ ├── PromptVersion* (one active; N candidates awaiting approval) │ └── Skill* (tools/playbooks attached to the agent) │ └── SkillVersion* (candidate → active lifecycle) └── Run history (outcomes aggregated per run) ``` * **Project** — the top-level workspace. Maps 1:1 to a MuBit instance in hosted deployments. * **Agent Card** — the configuration surface for one agent: identity + prompt + attached skills. * **PromptVersion** — a single version of an agent's system prompt. Exactly one is `active` at a time; others sit as `candidate`, `retired`, or `archived`. * **SkillVersion** — same lifecycle applied to a skill definition (parameters schema + instructions). ### Projects ```python Python # List projects accessible to this API key projects = client.list_projects() # Create a project project = client.create_project( name="triage-demo", description="Customer-support triage pilot", ) project_id = project["project"]["project_id"] # Update / delete client.update_project(project_id=project_id, description="Live now") client.delete_project(project_id=project_id) # tears down the backing instance ``` ```javascript JavaScript const { project } = await client.createProject({ name: "triage-demo", description: "Customer-support triage pilot", }); const projectId = project.project_id; await client.updateProject({ project_id: projectId, description: "Live now" }); await client.deleteProject({ project_id: projectId }); ``` ```rust Rust let resp = client.create_project(json!({ "name": "triage-demo", "description": "Customer-support triage pilot" })).await?; let project_id = resp["project"]["project_id"].as_str().unwrap(); ``` **In the console:** `Projects` tab in the left sidebar lists every project you can access. `New Project` opens the creation form; each row links to `/app/projects/` where you can rename, describe, or delete the project from its **Settings** tab. ### Agent Definitions Every Agent Card you see in the console is an `AgentDefinition` row in the control plane. ```python Python # Create an agent inside a project agent = client.create_agent_definition( project_id=project_id, agent_id="triage", role="customer triage agent", description="Routes escalations and captures recurring issues", system_prompt_content="You are a concise, empathetic triage agent...", ) # List agents in a project agents = client.list_agent_definitions(project_id=project_id) # Update role / description / prompt client.update_agent_definition( project_id=project_id, agent_id="triage", description="Now also handles billing escalations", ) ``` The `system_prompt_content` you pass on creation becomes the first `active` PromptVersion for the agent. Subsequent changes flow through `set_prompt` (manual) or `optimize_prompt` (LLM-generated candidate). **In the console:** open a project → **Agents** tab. Each row links to the agent's page at `/app/projects//agents/` with sub-tabs for **Identity**, **Prompts**, **Skills**, **Runs**, **Memory**, and **Play**. `New Agent` creates an `AgentDefinition` and mints the first active `PromptVersion` in one step. ### PromptVersion lifecycle Prompts don't just change — they **version**. Every write mints a new `PromptVersion` row, and exactly one version is `active` at any time. #### Statuses | Status | Meaning | | ----------- | ---------------------------------------------------------------------------------------------------- | | `active` | The version currently served to retrieval / inference | | `candidate` | Awaiting approval. Visible in the console's "Pending Optimization" card; gets a diff view vs. active | | `retired` | Previously active, superseded by a newer version | | `archived` | Manually archived (won't show up in default listings) | #### Sources | Source | How it was created | | -------------- | -------------------------------------------------------------------------------------------- | | `manual` | Written via `set_prompt` | | `optimization` | Minted by `optimize_prompt` — the control plane synthesises a candidate from recent outcomes | | `rollback` | Restored from a retired version | #### Typical flow ```python Python # Manual edit — activates immediately client.set_prompt( agent_id="triage", content="Updated system prompt...", activate=True, ) # Fetch current active prompt current = client.get_prompt(agent_id="triage") # List all versions for an agent versions = client.list_prompt_versions(agent_id="triage") # Ask the control plane to propose a candidate from recent outcomes candidate = client.optimize_prompt( agent_id="triage", project_id=project_id, llm_override={"provider": "anthropic", "model": "claude-sonnet-4-6"}, ) # Returns: { success, candidate, optimization_summary, confidence, activated } # Compare two versions (returns unified diff_text) diff = client.get_prompt_diff( agent_id="triage", version_a_id=current["prompt"]["version_id"], version_b_id=candidate["candidate"]["version_id"], ) # Approve the candidate client.activate_prompt_version( agent_id="triage", version_id=candidate["candidate"]["version_id"], ) ``` ```javascript JavaScript await client.setPrompt({ agent_id: "triage", content: "...", activate: true }); const versions = await client.listPromptVersions({ agent_id: "triage" }); const candidate = await client.optimizePrompt({ agent_id: "triage", project_id: projectId, }); await client.activatePromptVersion({ agent_id: "triage", version_id: candidate.candidate.version_id, }); ``` Each `PromptVersion` carries outcome aggregates (`avg_outcome_score`, `outcome_count`) so you can decide whether a candidate is actually an improvement before promoting it. See [Prompt Optimization Lifecycle](/recipes/prompt-optimization) for the end-to-end workflow. #### From the console The same lifecycle is available without writing code. Open an agent's **Prompts** tab at `/app/projects//agents//prompts`. | You want to… | Where to click | | ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Edit the active prompt by hand | `Edit` button on the **Active System Prompt** card → `Save & Create Version` (source: `manual`) | | Ask the optimizer for a candidate | `Suggest Optimization` button (sparkles icon) on the same card. Creates a new row with `status: candidate`, `source: optimization`, auto-expanded to show the new prompt | | Review a candidate's diff against active | `Review` on the pending-candidate banner, or `Compare` in the Version History table → opens `/app/projects//agents//compare/` with a unified diff and the candidate's optimization summary | | Approve a candidate | `Approve` in the banner or `Approve & Activate` on the compare page. Flips candidate → active and previous active → retired atomically | | Roll back | Find a `retired` row in Version History → `Compare` to confirm → approve that version. The activation is recorded with `source: rollback` | **The console uses the instance's default optimizer model.** If you need a specific provider/model/temperature for a single optimize run, call `client.optimize_prompt(..., llm_override={"provider": ..., "model": ..., "temperature": ...})` from the SDK — the console does not expose that override today. ### Skills Skills are tools or playbooks attached to a project (and optionally bound to a specific agent). They version identically to prompts. ```python Python # Create a tool skill skill = client.create_skill( project_id=project_id, agent_id="triage", # optional — omit for project-level shared skill name="send-email", description="Send a templated customer email", parameters_schema='{"type":"object","properties":{"to":{"type":"string"},"template":{"type":"string"}}}', instructions="Use for routine replies. Do not use for escalations.", skill_type="tool", # or "playbook" ) # List skills in a project skills = client.list_skills(project_id=project_id) # Ask the control plane to propose an improved version candidate = client.optimize_skill( project_id=project_id, skill_id=skill["skill"]["skill_id"], ) # Diff + activate just like prompts diff = client.get_skill_diff( skill_id=skill["skill"]["skill_id"], version_a_id="...", version_b_id="...", ) client.activate_skill_version( skill_id=skill["skill"]["skill_id"], version_id=candidate["candidate"]["version_id"], ) ``` `skill_type`: * `"tool"` — a callable function with a parameters schema. * `"playbook"` — a longer-form text description of a procedure the agent should follow. #### From the console Skills follow the same candidate → active → retired flow as prompts, wrapped in a UI at `/app/projects//skills/`: | You want to… | Where to click | | ------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Edit **Description**, **Parameters Schema**, or **Instructions** by hand | `Edit` on the **Active Definition** card → three dedicated fields → `Save & Create Version` | | Ask the optimizer for a candidate | `Suggest Optimization` on the same card. The diff view at `/app/projects//skills//compare/` shows changes across all three fields in one unified diff | | Approve | `Approve` on the pending-candidate banner, or `Approve & Activate` on the compare page | | Roll back | Pick a retired version from Version History → `Compare` → approve | Shared project-level skills live under `/app/projects//skills`; agent-scoped skills appear under the owning agent at `/app/projects//agents//skills`. ### Run history per project Every run your agents execute gets a row in the project's run history with aggregates: `lessons_extracted`, `prompt_changes`, `avg_outcome_score`, `outcome_count`, `ingest_count`. Use this to track how an agent's behavior evolves over time without querying raw memory. ```python Python runs = client.list_run_history(project_id=project_id, limit=50) run = client.get_run_history(project_id=project_id, run_id="run-abc") ``` **In the console:** each agent has a **Runs** tab (`/app/projects//agents//runs`) that renders the same run history with filters and drill-down. The project-level **Logs** tab (`/app/projects//logs`) spans all agents in the project. ### Try a prompt or skill before shipping it Every agent page has a **Play** tab at `/app/projects//agents//play`. It runs a real query against the project's instance and shows the response, retrieved memory, and invoked skills — the same trace an SDK call would see. Use it as a smoke test after activating a new prompt or skill version. **Caveat:** Play runs against whichever version is currently `active`, so you can dry-run a freshly-activated version but not a still-`candidate` one. To pressure-test a candidate specifically, briefly activate it in a non-production project, or use the advanced-panel `direct_bypass` mode to skip prompt routing. ### When to use the resource model vs. raw `run_id` | Use the resource model when… | Stick with raw `run_id` when… | | -------------------------------------------------------------------------- | --------------------------------------------------------- | | You want to version prompts + skills and roll back | You're prototyping against a single unmanaged endpoint | | Multiple teammates or CI workflows need to coordinate on an agent's config | Your agent config lives entirely in your application code | | You want the console's "Pending Optimization" review UX | You don't need a human-in-the-loop approval step | | You want per-project billing / observability boundaries | You only have one project and never expect to grow | ### Related pages * [Prompt Optimization Lifecycle (recipe)](/recipes/prompt-optimization) — end-to-end: capture outcomes → optimize → diff → activate. * [Control HTTP — Managed resources](/api-reference/control-http#managed-resources-projects-agents-skills-prompts) — raw wire contract. * [Control gRPC — Managed resources](/api-reference/control-grpc#managed-resources-projects-agents-skills-prompts) — proto RPCs. * [Authentication](/sdk/authentication) — API keys authorise against a specific project's instance. import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## Pub-Sub & Semantic Subscriptions Build event-driven workflows with run-scoped control streams and semantic subscriptions on the data plane. Polling state endpoints does not scale for reactive systems. MuBit exposes two streaming surfaces so workers wake on state changes instead of spinning. ### Decision model | Stream | Scope | Method | Best use | | ------------------------- | ------------- | ------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | Run-scoped control events | `run_id` | `client.subscribe(...)` (SSE or gRPC) | Workflow coordination inside one run: ingestion completion, checkpoints, lesson promotion, handoffs, outcome recording, drift signals | | Core data-plane pub-sub | User + filter | `client.core.subscribe_events(...)` (SSE or gRPC) | Topic-triggered automation across broader state: wake on node insert/update/delete, wake when a newly-written node crosses a semantic-similarity threshold | Both surfaces stream events as they happen. The subscription lives for the lifetime of the HTTP or gRPC stream — on disconnect the server cleans up the subscription automatically. ### Control event stream Run-scoped events fire for every meaningful control-plane state change: \~24 types covering ingest, reflection, checkpointing, outcome recording, handoffs, prompt activations, drift, project/agent/skill CRUD, run-monitor signals. Filter with `event_types` to keep the stream narrow. ```python Python # SSE over HTTP (client.subscribe is a thin wrapper around GET /v2/control/events/subscribe) stream = client.subscribe({ "run_id": run_id, "event_types": ["context.ingest_completed", "context.checkpoint_created"], }) for event in stream: print(event["type"], event["payload"]) ``` ```ts Node / TS const stream = await client.subscribe({ run_id: runId, event_types: ["context.ingest_completed", "context.checkpoint_created"], }); const reader = stream.body!.getReader(); const decoder = new TextDecoder(); while (true) { const { value, done } = await reader.read(); if (done) break; console.log(decoder.decode(value)); } ``` Events include `id`, `type`, `run_id`, `agent_id`, `payload` (event-specific JSON), and `created_at`. Recent events are replayable from Redis Streams (default retention 10,000 per run, env-configurable). ### Core semantic subscriptions `core.subscribe_events` opens an SSE (or server-streaming gRPC) connection that delivers pub-sub events as they are published by the storage layer. Every event yielded by the SDK has the same shape across Python, JavaScript, and Rust — and across both HTTP and gRPC transports. The first event on the stream is always `subscribed`; after that, events are `node.inserted`, `node.updated`, `node.deleted`, or `memory.added`. #### Event shape Every event is a dict / object with a `type` discriminator plus the fields relevant to that type. The SDK wrappers parse `metadata_json` / `entry_json` wire strings into native `metadata` / `entry` objects and strip proto3 scalar defaults so callers see a tight shape: | `type` | Fields | | -------------------------------- | ----------------------------------------------------------- | | `subscribed` | `subscription_id` | | `node.inserted` / `node.updated` | `node_id`, `run_id`, `metadata`, `created_at`, `updated_at` | | `node.deleted` | `node_id` | | `memory.added` | `session_id`, `entry` | Example stream: ```json {"type":"subscribed","subscription_id":42} {"type":"node.inserted","node_id":7,"run_id":"r1","metadata":{"intent":"fact"},"created_at":1714000000,"updated_at":1714000000} {"type":"node.deleted","node_id":7} ``` ```python Python # Fire only when a node whose vector is near the "billing escalation" embedding lands. events = client.core.subscribe_events({ "filter_type": "semantic", "query_text": "billing escalation", "threshold": 0.82, }) for event in events: if event["type"] == "subscribed": print("subscription id:", event["subscription_id"]) continue if event["type"] == "node.inserted": node_id = event["node_id"] # Wake the agent loop, page an on-call, enqueue a follow-up recall, … handle_billing_escalation(node_id) ``` ```rust Rust use serde_json::json; use tokio_stream::StreamExt; let mut stream = client.core.subscribe_events(json!({ "filter_type": "semantic", "query_text": "billing escalation", "threshold": 0.82 })).await?; while let Some(event) = stream.next().await { let evt = event?; if evt["type"] == "node.inserted" { let node_id = evt["node_id"].as_u64().unwrap_or(0); // react… } } ``` #### Filter types | `filter_type` | Wakes on | | ------------- | ------------------------------------------------------------------------------------------------------------------------ | | `"all"` | Any node insert/update/delete (use for admin tooling, not production) | | `"node"` | Events touching a single `node_id` (needs `node_id` in the request) | | `"semantic"` | Inserts/updates whose vector exceeds `threshold` cosine similarity to the encoded `query_text`. Default threshold `0.8`. | | `"session"` | `memory.added` events scoped to one session id | ACL filtering runs server-side: subscribers only see events for nodes they have permission to read. ### Cleanup and lifecycle * Close the SSE response / drop the gRPC stream to end the subscription. The server unsubscribes automatically — you do not need to call `unsubscribe` explicitly. * `POST /v2/core/pubsub/unsubscribe { subscription_id }` exists for admin tooling or for cleaning up subscriptions whose originating client was killed before it could close the stream. * `POST /v2/core/pubsub/list` returns the active subscription IDs for the calling user plus the server-wide count — useful for monitoring. ### Failure modes and troubleshooting | Symptom | Root cause | Fix | | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Too many events | Broad filter (`"all"`) or a low semantic threshold | Narrow `filter_type`, raise `threshold`, or filter client-side by `event_types` on the control stream | | Missing events | Subscriber scoped to a different `run_id` (control stream) or subscribed before the data plane had permission to read the emitting node (core stream) | Verify the producer and subscriber share a `run_id`; check ACLs on the node class you expect to see | | SSE stream idle for minutes | Proxy / load balancer idle timeout | The server emits SSE keep-alives, but some proxies still cut idle connections — configure the proxy for long-lived SSE or fall back to the gRPC surface | | Core subscribe returns 404 / denied | Data-plane access policy not enabled for the route | `/v2/core/pubsub/*` sits under the core route policy. See [Core Direct Lanes and Policy](/api-reference/core-direct-lanes) for the flag set and rollout guidance | ### Next steps * Add planner/specialist coordination at [Sessions and branching](/sdk/sessions-and-branching). * Review state APIs at [State management endpoints](/api-reference/state-management). * See the exact HTTP request/response shapes at [Core Direct Lanes — PubSub](/api-reference/core-direct-lanes#pubsub). import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## Query patterns Use routed retrieval, direct semantic search, and control filters for structured MuBit retrieval. MuBit provides several retrieval patterns depending on your use case: | Pattern | When to use | | ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | | `control.query` with `mode: "agent_routed"` | Default retrieval path — routes through the control plane for answer-oriented results | | `control.query` with `entry_types`, `include_working_memory`, `agent_id`, `user_id` | Scoped retrieval with fine-grained filters | | `control.context` | Model-ready context block with token budgeting before an LLM call | | `client.core.search(...)` | Direct semantic search when you need raw core hits (policy-gated) | ### Routed retrieval example ```python answer = client.recall( session_id="claim-123", query="What changed in the claimant's story after the second interview?", ) ``` ### Structured control query example ```python result = client.query( run_id="claim-123", query="Show failure lessons from prior fraud reviews", entry_types=["lesson", "rule"], include_working_memory=False, mode="agent_routed", ) ``` ### Direct semantic search example ```python results = client.core.search( query_text="windshield replacement invoice", k=5, run_id="claim-123", ) ``` ### Guidance * Prefer `control.query` for app and agent retrieval. * Prefer `control.context` before an LLM step. * Use direct semantic search only for advanced compatibility cases where you need raw core hits. * Treat `user_id` as a logical sub-scope tag only. It narrows retrieval inside the authenticated actor boundary and never expands access. import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## Rate Limits Per-instance quotas, the headers MuBit returns on every response, and how to size your client backoff. This page is a stub. Quotas and headers below match the current production defaults; canonical limits move into the OpenAPI spec when it lands. Rate limits are enforced **per instance, per route family**. The instance is the segment after `mbt_` in your API key (e.g. `mbt_us-east-1_…`). Requests from different keys to the same instance share the same bucket. ### Headers on every response | Header | Value | | ----------------------- | ----------------------------------------------------------- | | `X-RateLimit-Limit` | Total requests allowed in the current window | | `X-RateLimit-Remaining` | Requests left in the current window | | `X-RateLimit-Reset` | Unix epoch (seconds) when the window resets | | `X-RateLimit-Bucket` | Logical bucket name (`writes`, `reads`, `reflect`, `admin`) | | `Retry-After` | Seconds to wait — only on `429` responses | | `X-Request-ID` | Per-request id; log it | ### Default quotas | Bucket | Routes | Default limit | | --------- | ----------------------------------------------------------------- | ------------- | | `writes` | `remember`, `archive`, `record_outcome`, `feedback`, `checkpoint` | 600 / minute | | `reads` | `recall`, `get_context`, `dereference`, `lessons`, `list_agents` | 1200 / minute | | `reflect` | `reflect`, `surface_strategies`, `diagnose` | 60 / minute | | `admin` | `register_agent`, `handoff`, `forget` | 120 / minute | Quotas scale with paid plans. Contact support for higher limits. ### Reading the headers ```python import mubit client = mubit.Client() resp = client.remember(...) # Headers are surfaced on the underlying transport — peek via: print(client.last_response_headers["X-RateLimit-Remaining"]) ``` The SDK also tracks the headers internally and slows down preemptively when `X-RateLimit-Remaining` falls below 10% of `X-RateLimit-Limit`. Disable with `Client(adaptive_backoff=False)`. ### Hitting `429` The SDK retries `429` responses by default, respecting the `Retry-After` header. If you've disabled SDK retries, handle it like this: ```python from mubit.errors import RateLimited try: client.remember(...) except RateLimited as e: time.sleep(e.retry_after_ms / 1000) # retry ``` See [Retries and idempotency](/sdk/retries) for the full retry pattern. ### Reducing pressure * Batch `remember()` calls when ingesting in bulk — use `control.ingest` with a list rather than N synchronous `remember()` calls. * Cache `get_context()` results within a single LLM request — the same context is usually safe to reuse across tool calls in the same turn. * Use `recall(limit=N)` to bound result sizes; smaller results cost the same as larger ones at the API layer but reduce downstream token spend. ### See also * [Errors](/api-reference/errors) — `429` payload shape * [Retries](/sdk/retries) — backoff strategy import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## Retries and Idempotency When to retry MuBit calls, how to make writes safe to repeat, and what the SDK does for you automatically. This page is a stub. The retry behaviour described here matches the current SDK defaults; canonical guarantees move into the OpenAPI spec when it lands. ### What the SDK retries automatically By default the SDK retries on: * `429` (rate-limited) — respects `retry_after_ms` if present * `500` / `502` / `503` / `504` — exponential backoff with jitter * Network-level errors before any response was received (connect timeout, TCP reset) It does **not** retry on `400` / `401` / `403` / `404` / `409` / `422`. Those are caller errors — retrying makes them worse. Default policy: 3 attempts, base delay 250 ms, factor 2, jitter ±25%. Override per call: ```python from mubit import Client, RetryPolicy client = Client(retry_policy=RetryPolicy(max_attempts=5, base_ms=500)) ``` ### Idempotency keys Writes are safe to retry only if you pass an `Idempotency-Key`. The SDK injects one automatically for `remember`, `archive`, `register_agent`, `handoff`, `feedback`, and `record_outcome`. The key is derived from a hash of the request body plus a per-process random salt — re-running the same call within a 24-hour window returns the same `record_id` instead of creating a duplicate. To pin a key explicitly (e.g. to dedupe across retries from a queue worker): ```python client.remember( session_id=run_id, agent_id="support-agent", content="…", intent="fact", idempotency_key=f"ticket-{ticket_id}-fact-1", ) ``` Idempotency keys are scoped per-instance, per-route. Reusing a key on a different route is a no-op. ### When to retry yourself Retry the SDK call if: * The exception is a `RateLimited`, `ServerError`, or `UpstreamError` and your retry budget allows. * You're inside an outer transaction whose commit boundary you control. Don't retry if: * The exception is a `ValidationError`, `PermissionError`, `AuthenticationError`, or `NotFound`. Fix the call. * The error came back without a `request_id` and the response body is empty. The request never reached MuBit — the underlying network error is the actual issue. ### Recommended pattern ```python from mubit.errors import RateLimited, ServerError, UpstreamError import time, random def with_retry(fn, max_attempts=4, base_ms=300): for attempt in range(max_attempts): try: return fn() except RateLimited as e: time.sleep(e.retry_after_ms / 1000) except (ServerError, UpstreamError): time.sleep(base_ms * (2 ** attempt) * (0.75 + random.random() * 0.5) / 1000) raise RuntimeError("retries exhausted") ``` The SDK's built-in retries cover most cases; this pattern is for queue workers, batch jobs, and other contexts where you want a longer outer budget than the SDK default. ### See also * [Errors](/api-reference/errors) — full error taxonomy * [Rate limits](/sdk/rate-limits) — quota headers and limits import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## Retrieve data Use recall for answer-oriented retrieval and getContext for model-ready context assembly, with raw query only for explicit control. MuBit exposes two default read patterns: * `recall()` when you want MuBit to answer a question from memory * `getContext()` / `get_context()` when you want a context block to send to your own model Use raw `control.query` when you need explicit control over low-level request fields or want to debug wire-level behavior. ### Retrieval decision model | Need | Recommended method | | --------------------------------------- | ------------------------------------------------------- | | Answer a question from stored memory | `recall()` | | Build a prompt-ready context block | `getContext()` / `get_context()` | | Inspect memory quality and gaps | `diagnose()` and `memoryHealth()` / `memory_health()` | | Explicit raw control over query payload | `client.query(...)` | | Compatibility-only direct lanes | `client.core.*` advanced routes when explicitly enabled | ### Helper-first retrieval examples ```python title="retrieve_helper.py" def retrieve_helper(client, run_id: str, question: str): answer = client.recall( session_id=run_id, query=question, entry_types=["fact", "lesson", "rule"], ) context = client.get_context( session_id=run_id, query=question, mode="summary", max_token_budget=400, ) return answer, context ``` ```ts title="retrieve_helper.ts" export async function retrieveHelper(client, runId: string, question: string) { const answer = await client.recall({ session_id: runId, query: question, entry_types: ["fact", "lesson", "rule"], }); const context = await client.getContext({ session_id: runId, query: question, mode: "summary", max_token_budget: 400, }); return { answer, context }; } ``` ```rust title="retrieve_helper.rs" use mubit_sdk::{GetContextOptions, RecallOptions}; pub async fn retrieve_helper( client: &mubit_sdk::Client, run_id: &str, question: &str, ) -> Result<(serde_json::Value, serde_json::Value), Box> { let mut recall = RecallOptions::new(question); recall.run_id = Some(run_id.to_string()); recall.entry_types = vec!["fact".to_string(), "lesson".to_string(), "rule".to_string()]; let answer = client.recall(recall).await?; let mut context = GetContextOptions::default(); context.run_id = Some(run_id.to_string()); context.query = Some(question.to_string()); context.mode = Some("summary".to_string()); context.max_token_budget = Some(400); let context = client.get_context(context).await?; Ok((answer, context)) } ``` ### What the helper path buys you * `recall()` uses the current control-plane query contract without you assembling the raw payload manually. * `getContext()` / `get_context()` gives you sectioned, cache-friendly context assembly with optional budget control. * `diagnose()` and `memoryHealth()` / `memory_health()` help you understand why retrieval is weak before you start changing prompts blindly. ### Failure modes and troubleshooting | Symptom | Root cause | Fix | | ---------------------------------- | ---------------------------------------------------------------- | ----------------------------------------------------- | | Answer ignores an important lesson | Entry types or context budget too broad | Restrict entry types and use `getContext()` budgeting | | Retrieval feels noisy | You are mixing compatibility-only direct lanes with routed paths | Standardize one default read path per workflow | | You need wire-level parity checks | Helper abstracts too much | Drop to raw `client.query(...)` for that case | ### Next steps * Inspect the helper and raw surface split at [SDK methods](/sdk/sdk-methods). * Review the full public control surface at [Control HTTP reference](/api-reference/control-http). import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## SDK Configuration Reference Environment variables, transport selection, endpoint defaults, and connection options for MuBit SDKs. All MuBit SDKs (Python, Node.js, Rust) share a consistent configuration model based on environment variables and constructor options. ### Environment variables | Variable | Required | Default | Description | | --------------------- | -------- | ----------------------- | -------------------------------------------------------------------------- | | `MUBIT_API_KEY` | yes | — | API key in format `mbt___` | | `MUBIT_ENDPOINT` | no | `https://api.mubit.ai` | Base endpoint for both HTTP and gRPC (SDK resolves protocol automatically) | | `MUBIT_TRANSPORT` | no | `auto` | Transport selection: `auto`, `http`, or `grpc` | | `MUBIT_HTTP_ENDPOINT` | no | `https://api.mubit.ai` | Override HTTP-specific endpoint | | `MUBIT_GRPC_ENDPOINT` | no | `grpc.api.mubit.ai:443` | Override gRPC-specific endpoint | When `MUBIT_ENDPOINT` is set, it serves as the default for both HTTP and gRPC. The protocol-specific overrides (`MUBIT_HTTP_ENDPOINT`, `MUBIT_GRPC_ENDPOINT`) take precedence when set. ### Transport selection The `transport` option controls how the SDK communicates with MuBit: | Value | Behavior | | ---------------- | --------------------------------------------------------------------------- | | `auto` (default) | SDK probes the endpoint and selects gRPC if available, falling back to HTTP | | `http` | Force HTTP/REST transport | | `grpc` | Force gRPC transport | ```python from mubit import Client # Auto transport (default) client = Client(api_key="mbt_...") # Force gRPC client = Client(api_key="mbt_...", transport="grpc") # Force HTTP with custom endpoint client = Client( api_key="mbt_...", transport="http", endpoint="https://custom.api.mubit.ai", ) ``` ```js import { Client } from "@mubit-ai/sdk"; // Auto transport (default) const client = new Client({ api_key: process.env.MUBIT_API_KEY }); // Force gRPC const client = new Client({ api_key: "mbt_...", transport: "grpc" }); // Force HTTP with custom endpoint const client = new Client({ api_key: "mbt_...", transport: "http", endpoint: "https://custom.api.mubit.ai", }); ``` ```rust use mubit_sdk::{Client, ClientConfig}; // From environment (reads MUBIT_API_KEY, MUBIT_ENDPOINT, etc.) let client = Client::connect(ClientConfig::from_env()?).await?; // Explicit gRPC let config = ClientConfig::new("grpc.api.mubit.ai:443") .api_key("mbt_...") .transport("grpc"); let client = Client::connect(config).await?; ``` ### Endpoint defaults | Context | HTTP endpoint | gRPC endpoint | | ----------------------- | -------------------------- | --------------------------- | | **Hosted (production)** | `https://api.mubit.ai` | `grpc.api.mubit.ai:443` | | **Hosted (dev)** | `https://api.dev.mubit.ai` | `grpc.api.dev.mubit.ai:443` | | **Local** | `http://127.0.0.1:3000` | `127.0.0.1:50051` | Both hosted endpoints use TLS. Local endpoints are plaintext by default. ### Constructor options Beyond environment variables, SDK constructors accept these options: | Option | Type | Description | | ----------- | ------ | ------------------------------------------------- | | `api_key` | string | API key (overrides `MUBIT_API_KEY`) | | `endpoint` | string | Base endpoint (overrides `MUBIT_ENDPOINT`) | | `transport` | string | Transport selection (overrides `MUBIT_TRANSPORT`) | | `run_id` | string | Default run ID for all operations | | `agent_id` | string | Default agent ID | | `user_id` | string | Default user ID for scoping | ### Resolution order Configuration resolves in this order (first wins): 1. Explicit constructor argument 2. Protocol-specific env var (`MUBIT_HTTP_ENDPOINT` / `MUBIT_GRPC_ENDPOINT`) 3. Base env var (`MUBIT_ENDPOINT`) 4. Built-in default (`https://api.mubit.ai`) ### Next steps * See [Getting started](/getting-started) for quick setup. * See [gRPC Transport Guide](/sdk/grpc-transport) for gRPC-specific guidance. * See [Control HTTP reference](/api-reference/control-http) for the full HTTP API surface. import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## SDK Helpers Explicit helper methods for memory, context assembly, and the learning loop — when you outgrow the mubit.learn drop-in. The `mubit.learn` drop-in is the right choice for most readers. Use the helpers below when you need explicit control over what gets stored, when, and under which session/agent identity. ### Helper map | Method | Purpose | When to reach for it | | ------------------ | -------------------------------------------------------------------------------- | -------------------------------------------------- | | `remember()` | Default write path for a single logical memory item | Storing facts, lessons, or observations | | `recall()` | Answer-oriented retrieval (returns a synthesized `final_answer` plus `evidence`) | Asking the memory a question | | `get_context()` | Assemble a token-budgeted context block to inject into your next LLM call | Before any prompt where memory matters | | `archive()` | Store an exact artifact with a stable `reference_id` | Anything you'll need recovered byte-for-byte later | | `dereference()` | Fetch the exact content for a `reference_id` | Recover an archived artifact | | `reflect()` | Extract reusable lessons from session evidence | End of a run/session, or on demand | | `lessons()` | List lessons with optional filtering | Audit, debugging, surfacing strategies | | `checkpoint()` | Snapshot memory state with a label | Before compaction or risky transitions | | `record_outcome()` | Tag a `reference_id` with `success` / `failure` and a rationale | RL-style reinforcement signal | ### Minimal usage ```python title="getting_started.py" import os from mubit import Client run_id = "support:acme:ticket-42" client = Client( api_key=os.environ["MUBIT_API_KEY"], run_id=run_id, transport=os.getenv("MUBIT_TRANSPORT", "auto"), ) client.remember( session_id=run_id, agent_id="support-agent", content="Customer Taylor prefers concise Friday updates.", intent="fact", metadata={"customer": "taylor", "source": "quickstart"}, ) answer = client.recall( session_id=run_id, query="What update style does Taylor want?", entry_types=["fact", "lesson", "rule"], ) context = client.get_context( session_id=run_id, query="Draft the next customer update.", mode="summary", max_token_budget=300, ) print(answer["final_answer"]) print(context["section_summaries"]) ``` ### Cross-session recall `recall()` queries are scoped to the session by default. To read memory from a different session for the same user: * Store the memory as a **lesson** with `lesson_scope="global"`. * On recall, pass the same `user_id` and `entry_types=["lesson"]`. ```python client.remember( session_id="s1", agent_id="support-agent", user_id="taylor-1", content="Taylor prefers concise written updates on Friday afternoons.", intent="lesson", lesson_scope="global", ) # Different session, same user_id — recall returns the lesson. client.recall( session_id="s2", agent_id="support-agent", user_id="taylor-1", query="how does Taylor like updates?", entry_types=["lesson"], ) ``` `intent="fact"` memories are session-local even if `user_id` matches. This trips up almost everyone the first time — call it out in your design doc. ### Context modes `get_context(mode=...)` returns different shapes: | Mode | Returns | Use when | | ------------- | --------------------------------------------------------- | ---------------------------------------------------------- | | `"full"` | Single concatenated `context_block` string | You want to drop the result straight into a system message | | `"summary"` | `section_summaries[]` with `top_item_preview` per section | You want to render structured context (e.g., a card UI) | | `"sectioned"` | Sections + per-section text | You want to interleave sections with your own prose | `max_token_budget` is enforced after assembly. If the budget is too small, the lowest-score evidence is dropped first. ### Exact references `archive()` and `dereference()` are the exact-recovery pair. Use them for anything semantic recall is the wrong tool for — original diffs, raw tool outputs, generated SQL you'll re-execute later. ```python archived = client.archive( session_id=run_id, agent_id="support-agent", content="Original billing diff and remediation note", artifact_kind="billing_postmortem", labels=["billing", "exact"], ) # Later — possibly in a different session — fetch the exact content back. exact = client.dereference( session_id=run_id, reference_id=archived["reference_id"], ) ``` `archive()` requires the `archive_block` write scope; `dereference()` requires the matching read scope. Register your agent with both if it needs to round-trip artifacts. ### Reflection and outcomes ```python ref = client.reflect(session_id=run_id) for L in ref["lessons"]: print(f"[{L['lesson_type']}] {L['content']}") client.record_outcome( session_id=run_id, reference_id="ticket-42", outcome="success", rationale="Customer confirmed the SSO bypass resolved the ticket.", ) ``` `reflect()` extracts new lessons from the session's traces. `record_outcome()` is the reinforcement signal — recurring `success` lessons get promoted from run-scoped to session-scoped to global. ### When to drop down to `client.*` raw methods Reach for `client.control.*` only when you need: * Async ingest with explicit job polling (`control.ingest` + `control.get_ingest_job`). * Raw wire payloads for tooling/observability that needs the full response shape. * Direct access to `core.*` lanes (search, scratch, branching) that the helpers wrap. Most application code never needs this layer. See the [Control HTTP reference](/api-reference/control-http) for the full surface. import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## SDK methods Three integration layers — from zero-config closed-loop to full low-level control. MuBit SDKs expose three layers: | Layer | What it does | When to use | | ------------------------------------------- | --------------------------------------------------------------------- | ------------------------------------------------------------- | | **Learn** (`mubit.learn` / `learn`) | Auto-ingest all LLM interactions + auto-inject lessons + auto-reflect | Zero-config closed-loop — agents learn with one line of setup | | **Helpers** on `Client` | 17 explicit methods for memory, context, reflection, multi-agent | Fine-grained control over what gets remembered and when | | **Raw domains** (`auth`, `control`, `core`) | 1:1 endpoint mappings | Wire debugging, async job polling, advanced routes | Start with learn or helpers. Drop to raw domains only when you need exact payload control. ### Learn module (closed-loop) Before each LLM call, the learn module retrieves relevant lessons from MuBit and injects them into the system message. After the call, the interaction is ingested automatically. On run end, reflection extracts new lessons. ```python import mubit.learn mubit.learn.init(api_key="mbt_...", agent_id="my-agent") # All OpenAI/Anthropic/LiteLLM calls now auto-inject lessons + auto-ingest. @mubit.learn.run(agent_id="planner", auto_reflect=True) def plan_task(task): return openai.OpenAI().chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": task}], ).choices[0].message.content ``` ```js import { learn, wrapOpenaiLearn } from "@mubit-ai/sdk/learn"; const runManager = learn.init({ apiKey: "mbt_...", agentId: "my-agent" }); // wrapOpenaiLearn(client, { learnConfig, lessonCache, learnClient, runManager }); const result = await learn.withRun({ agentId: "planner" }, async () => { return client.chat.completions.create({ ... }); }); ``` ```rust use mubit_sdk::learn::{LearnSession, LearnConfig}; let session = LearnSession::new(LearnConfig::from_env().agent_id("my-agent")).await; let enriched = session.enrich_messages(&messages).await; // Make LLM call with enriched... session.record("response", "gpt-4o", 1500.0).await; session.end().await; ``` ### Helper method bundles | Use case | Methods | What they do | | ---------------- | ------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Basic memory | `remember`, `recall` | Ingest content with intent classification; semantic query with evidence scoring | | Prompt context | `getContext` / `get_context` | Token-budgeted context block for LLM injection (rules → lessons → facts) | | Exact artifacts | `archive`, `archiveBlock` / `archive_block`, `dereference` | Bit-exact storage with stable reference IDs; retrieval without semantic search | | Run lifecycle | `checkpoint`, `reflect`, `recordOutcome` / `record_outcome`, `recordStepOutcome` / `record_step_outcome` | Durable state; LLM lesson extraction; reinforcement feedback; per-step process rewards | | Ingest lifecycle | `getIngestJob` / `get_ingest_job`, `getRunIngestStats` / `get_run_ingest_stats` | Poll async ingest jobs ([details](#ingest-job-tracking)); per-run counters. `remember()` and `ingest()` return a `job_id` — call `get_ingest_job(job_id)` until `done=True` before reading the same run. | | Multi-agent | `registerAgent` / `register_agent`, `listAgents` / `list_agents`, `handoff`, `feedback` | Scoped access per agent; task transfer | | Diagnostics | `memoryHealth` / `memory_health`, `diagnose`, `surfaceStrategies` / `surface_strategies`, `forget` | Staleness metrics; error debugging; lesson clustering; deletion | | Activity & audit | `listActivity` / `list_activity`, `exportActivity` / `export_activity`, `appendActivity` / `append_activity` | Chronological activity browse, JSONL export, manual trace append | ### The learning loop ``` 1. remember() → ingest facts, traces, lessons 2. record_step_outcome() → per-step process reward signals (optional, for dense RL) 3. reflect() → LLM extracts lessons from evidence (auto-promotes recurring lessons: run → session → global) 4. get_context() → retrieve relevant lessons for the next call 5. record_outcome() → reinforce what worked at the run level ``` With learn, steps 1–3 happen automatically. With helpers, you orchestrate them yourself. ### Current helper catalog * `remember` * `recall` * `archive` * `archive_block` * `dereference` * `get_context` * `memory_health` * `diagnose` * `reflect` * `forget` * `checkpoint` * `register_agent` * `list_agents` * `record_outcome` * `record_step_outcome` * `surface_strategies` * `handoff` * `feedback` * `get_ingest_job`, `get_run_ingest_stats` — poll async ingest and inspect per-run counters * `mubit.auto` for automatic trace capture (ingestion only) * `mubit.learn` for closed-loop auto-inject + auto-ingest + auto-reflect (supports `auto_extract=True` for heuristic extraction) * `remember` * `recall` * `archive` * `archiveBlock` * `dereference` * `getContext` * `memoryHealth` * `diagnose` * `reflect` * `forget` * `checkpoint` * `registerAgent` * `listAgents` * `recordOutcome` * `recordStepOutcome` * `surfaceStrategies` * `handoff` * `feedback` * `getIngestJob`, `getRunIngestStats` — poll async ingest and inspect per-run counters * `learn.init`, `learn.withRun`, `learn.startRun` for closed-loop memory * `remember` * `recall` * `archive` * `archive_block` * `dereference` * `get_context` * `memory_health` * `diagnose` * `reflect` * `forget` * `checkpoint` * `register_agent` * `list_agents` * `record_outcome` * `record_step_outcome` * `surface_strategies` * `handoff` * `feedback` * `get_ingest_job`, `get_run_ingest_stats` — poll async ingest and inspect per-run counters * `learn::LearnSession` with `enrich_messages` / `record` / `end` for closed-loop memory ### Step-level outcomes Record per-step process rewards for dense RL signal within a run. Use after each agentic step, then reflect with `include_step_outcomes=True`. ```python client.record_step_outcome( step_id="tool_call_1", step_name="search_api", outcome="success", signal=0.8, rationale="Found the correct document on first try", directive_hint="Keep using search before browsing", ) # Later: reflect with step outcomes included client.reflect(include_step_outcomes=True) ``` ### Lane-scoped memory Lanes partition memory within a shared run so each agent sees only relevant entries. ```python # Ingest into a specific lane client.remember(content="Planning output: task A depends on B", intent="fact", lane="planning") # Query only the planning lane result = client.recall(query="task dependencies", lane="planning") # Register an agent with lane participation client.register_agent(agent_id="planner", role="planner", shared_memory_lanes=["planning", "shared"]) ``` `lane` (MAS memory isolation) is distinct from `direct_lane` (core data-plane retrieval routing). They serve different purposes and do not interact. ### Step-wise reflection Scope reflection to recent evidence or a specific step for incremental lesson extraction. ```python # Reflect over only the 5 most recent items client.reflect(last_n_items=5) # Reflect on a specific step, including step outcomes client.reflect(step_id="tool_call_1", include_step_outcomes=True) ``` ### Auto-extraction in learn Enable heuristic extraction of rules, lessons, and facts from LLM responses without an extra LLM call. ```python mubit.learn.init( api_key="mbt_...", agent_id="my-agent", auto_extract=True, # extract structured items from LLM responses extraction_mode="heuristic", # no LLM call needed ) ``` ### When to use what | Scenario | Use | | ------------------------------------ | -------------------------------------------------------------------------------- | | Agents should learn with zero code | `mubit.learn.init()` (Python), `learn.init()` (JS), `LearnSession::new()` (Rust) | | Passive trace capture only | `mubit.auto.instrument()` (Python only) | | Control exactly what gets remembered | `client.remember()` + `client.recall()` | | Token-budgeted context for prompts | `client.get_context()` / `client.getContext()` | | Multiple agents with scoped access | `client.register_agent()` + `client.handoff()` | | Bit-exact artifact storage | `client.archive()` + `client.dereference()` | | Wire-level debugging | `client.*` / `client.core.*` | ### When to use raw control methods directly Use `client.*` when you need one of these explicitly: * `control.ingest` plus `get_ingest_job` job polling * `control.batch_insert` * exact raw request/response debugging against HTTP or gRPC * advanced or compatibility state-management routes * `control.list_activity`, `control.export_activity`, `control.append_activity` for audit trail * `control.get_ingest_job`, `control.get_run_ingest_stats` for job polling * `control.list_runs`, `control.link_run`, `control.unlink_run`, `control.delete_run` for run management * `control.get_run_snapshot` / `control.contextSnapshot` for full context snapshots #### Activity and audit trail Use `client.listActivity()` / `list_activity()` to browse chronological memory entries with scope and type filters. Use `exportActivity()` / `export_activity()` for JSONL export. Use `appendActivity()` / `append_activity()` to manually append activity traces. ```python # List recent activity for a run activity = client.list_activity(run_id="my-run", limit=50) # Export as JSONL export = client.export_activity(run_id="my-run", format="jsonl") # Append a manual activity trace client.append_activity(run_id="my-run", entries=[ {"type": "observation", "content": "Agent restarted after timeout"} ]) ``` ```js const activity = await client.listActivity({ runId: "my-run", limit: 50 }); const exported = await client.exportActivity({ runId: "my-run", format: "jsonl" }); await client.appendActivity({ runId: "my-run", entries: [ { type: "observation", content: "Agent restarted after timeout" } ]}); ``` #### Ingest job tracking `remember()` and the raw `ingest()` are **asynchronous** — they return a `job_id` and return immediately. If the next step in your code reads the same run (for example, `recall()` or `get_context()`), poll `get_ingest_job(job_id)` until `done=True` to guarantee the new item is visible. Framework adapters that advertise "synchronous put" semantics (e.g. `MubitStore` in `mubit-langgraph`) do this polling for you; the raw SDK does not. Retrieve per-run statistics with `get_run_ingest_stats()` when you want aggregate counts without scanning activity. ```python # Submit + poll until the job is durable job_resp = client.ingest({"run_id": "my-run", "items": [{"text": "…", "intent": "fact"}]}) while True: status = client.get_ingest_job(job_id=job_resp["job_id"]) if status.get("done"): break # Per-run aggregate stats stats = client.get_run_ingest_stats(run_id="my-run") ``` ```js const { job_id } = await client.ingest({ run_id: "my-run", items: [{ text: "…", intent: "fact" }] }); while (!(await client.getIngestJob({ jobId: job_id })).done) { // yield back briefly; in production use a small backoff } const stats = await client.getRunIngestStats({ runId: "my-run" }); ``` For bulk writes, prefer one `ingest({ items: [...] })` call with a list of items over many `remember()` calls — it is one job instead of N. #### Run management List, link, unlink, and delete runs. ```python # List recent runs runs = client.list_runs() # Link a child run to a parent client.link_run(run_id="parent-run", linked_run_id="child-run") # Unlink client.unlink_run(run_id="parent-run", linked_run_id="child-run") # Delete a run and its data client.delete_run(run_id="old-run") ``` ```js const runs = await client.listRuns(); await client.linkRun({ runId: "parent-run", linkedRunId: "child-run" }); await client.unlinkRun({ runId: "parent-run", linkedRunId: "child-run" }); await client.deleteRun({ runId: "old-run" }); ``` #### Context snapshot Retrieve a full context snapshot for a run, including working memory, attention state, and active goals. ```python snapshot = client.get_run_snapshot(run_id="my-run") ``` ```js const snapshot = await client.contextSnapshot({ runId: "my-run" }); ``` ### Temporal and quality features #### Occurrence time MuBit tracks two time dimensions for every memory entry: **ingestion time** (when the system learned it) and **occurrence time** (when the event actually happened). Set `occurrence_time` to record when an event happened, separate from when it was ingested. ```python import time # Event happened 3 days ago, ingested now client.remember( content="New CI/CD pipeline reduced deployment time by 60%.", intent="fact", occurrence_time=int(time.time()) - 86400 * 3, ) # Historical event from January 2025 client.remember( content="Server migration to AWS completed with zero downtime.", intent="fact", occurrence_time=1736899200, # Jan 15 2025 UTC ) ``` ```js // Event happened 3 days ago, ingested now await client.remember({ content: "New CI/CD pipeline reduced deployment time by 60%.", intent: "fact", occurrence_time: Math.floor(Date.now() / 1000) - 86400 * 3, }); // Historical event from January 2025 await client.remember({ content: "Server migration to AWS completed with zero downtime.", intent: "fact", occurrence_time: 1736899200, // Jan 15 2025 UTC }); ``` #### Temporal queries Use `min_timestamp` and `max_timestamp` to filter evidence to a specific time window. The filter checks `occurrence_time` first, falling back to ingestion time. ```python # "What happened in January 2025?" results = client.recall( query="What technical changes were made?", min_timestamp=1735689600, # Jan 1 2025 max_timestamp=1738367999, # Jan 31 2025 ) for evidence in results["evidence"]: print(f" {evidence['content'][:80]}") ``` ```js const results = await client.recall({ query: "What technical changes were made?", min_timestamp: 1735689600, max_timestamp: 1738367999, }); results.evidence.forEach(e => console.log(e.content.slice(0, 80))); ``` Without temporal bounds, queries like "What happened last week?" use natural language temporal intent detection and prioritize entries by occurrence time in the recency ranking. #### Search budget The `budget` parameter controls the depth of retrieval. Use `"low"` for real-time agents and `"high"` for accuracy-critical offline analysis. | Budget | Behavior | Typical latency | | -------- | --------------------------------------- | --------------- | | `"low"` | Fewer candidates, skip deep traversal | \< 500ms | | `"mid"` | Standard retrieval (default) | 500ms–2s | | `"high"` | More candidates, deeper graph traversal | 1–5s | ```python # Fast retrieval for a real-time chatbot fast = client.recall(query="user question", budget="low") # Deep retrieval for a research report deep = client.recall(query="comprehensive analysis topic", budget="high") ``` ```js // Fast const fast = await client.recall({ query: "user question", budget: "low" }); // Deep const deep = await client.recall({ query: "analysis topic", budget: "high" }); ``` #### Staleness detection When a newer fact contradicts an older one, MuBit marks the older entry as stale and deprioritizes it in ranking. The staleness metadata is available in evidence responses. ```python results = client.recall(query="Where is the office?") for evidence in results["evidence"]: stale = evidence.get("is_stale", False) status = " [STALE]" if stale else "" print(f" {evidence['content'][:60]}{status}") ``` ```js const results = await client.recall({ query: "Where is the office?" }); results.evidence.forEach(e => { const status = e.is_stale ? " [STALE]" : ""; console.log(` ${e.content.slice(0, 60)}${status}`); }); ``` Stale entries are still returned for transparency. The ranking penalty ensures they appear below the current fact. Filter them out in your application if you only want current information. #### Mental models The `mental_model` entry type stores consolidated entity summaries that are prioritized over raw facts in context assembly. Use this for entities your agent tracks over time. ```python client.remember( content="Alice Chen is a senior engineer specializing in distributed systems. " "She prefers async communication and reviews PRs within 24 hours.", intent="mental_model", metadata={"entity": "alice chen", "consolidated": True}, ) ``` ```js await client.remember({ content: "Alice Chen is a senior engineer specializing in distributed systems. " + "She prefers async communication and reviews PRs within 24 hours.", intent: "mental_model", metadata: { entity: "alice chen", consolidated: true }, }); ``` Mental models are returned with higher priority than individual facts in `recall()` and `get_context()`. Update them periodically as your agent learns more about an entity. ### Failure modes and troubleshooting | Symptom | Root cause | Fix | | ------------------------------------------- | -------------------------------------- | -------------------------------------------------------- | | SDK usage becomes inconsistent across teams | Raw and helper paths mixed arbitrarily | Set helpers as the default integration contract | | Debugging a route contract is awkward | Helper layer hides wire details | Use the raw `client.*` call for that investigation | | Docs and examples drift from SDK reality | Helpers undocumented | Treat the top-level helper surface as the public default | ### Next steps * See the concrete helper flow at [Getting started](/getting-started). * See the wire contract at [Control HTTP reference](/api-reference/control-http). import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## Sessions & Branching Use checkpoints for durable task continuity and core sessions for reversible scratch branches. Current MuBit uses two distinct ideas here: * **control-plane checkpoints** for durable task continuity and compaction survival * **core session branches** for temporary scratch or what-if work These are not the same thing. Checkpoints preserve the durable task path. Core sessions give you a temporary branch you can inspect, commit, or drop. ### Decision model | Requirement | Primitive | | ---------------------------------------- | ------------------------------------------ | | Preserve the main task before compaction | `checkpoint()` | | Try a temporary branch and discard it | `core.create_session`, `core.drop_session` | | Keep a reviewed branch | `core.commit_session` | | Keep a short-lived scratch buffer | `core.add_memory` with `ttl_seconds` | ### Minimal implementation example ```python title="sessions_and_branching.py" run_id = "claims:planner:claim-42" client.checkpoint( session_id=run_id, label="before-scratch-branch", context_snapshot="Planner is about to compare two claim settlement strategies.", ) base = client.core.create_session({}) branch = client.core.create_session({"parent_session_id": base["session_id"]}) branch_id = branch["session_id"] client.core.commit_session({"id": branch_id, "session_id": branch_id, "merge_strategy": "overwrite"}) client.core.drop_session({"id": branch_id, "session_id": branch_id}) ``` ```ts title="sessions_and_branching.ts" const runId = "claims:planner:claim-42"; await client.checkpoint({ session_id: runId, label: "before-scratch-branch", context_snapshot: "Planner is about to compare two claim settlement strategies.", }); const base = await client.core.createSession({}); const branch = await client.core.createSession({ parent_session_id: base.session_id }); await client.core.commitSession({ id: branch.session_id, session_id: branch.session_id, merge_strategy: "overwrite", }); await client.core.dropSession({ id: branch.session_id, session_id: branch.session_id, }); ``` ```rust title="sessions_and_branching.rs" use mubit_sdk::CheckpointOptions; use serde_json::json; let run_id = "claims:planner:claim-42"; let mut checkpoint = CheckpointOptions::new( "before-scratch-branch", "Planner is about to compare two claim settlement strategies.", ); checkpoint.run_id = Some(run_id.to_string()); client.checkpoint(checkpoint).await?; let base = client.core.create_session(json!({})).await?; let branch = client.core.create_session(json!({ "parent_session_id": base["session_id"] })).await?; let branch_id = branch["session_id"].as_str().unwrap(); client.core.commit_session(json!({ "id": branch_id, "session_id": branch_id, "merge_strategy": "overwrite" })).await?; client.core.drop_session(json!({ "id": branch_id, "session_id": branch_id })).await?; ``` ### Failure modes and troubleshooting | Symptom | Root cause | Fix | | ------------------------------------- | ---------------------------------- | ------------------------------------ | | Main path becomes hard to restore | No checkpoint was taken | Checkpoint before the scratch branch | | Branch writes leak into the main path | Branch was committed too early | Commit only reviewed branches | | Scratch context disappears too soon | TTL or session lifecycle too short | Adjust branch lifetime intentionally | ### Next steps * Apply the full branching workflow at [Branching memory](/recipes/branching-memory). * Add coordination at [Multi-agent shared state](/recipes/multi-agent-shared-state). import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## Troubleshooting Common issues, error messages, and solutions for MuBit SDK and API usage. Common issues and solutions when working with MuBit SDKs and APIs. ### Authentication #### Invalid API key **Symptom:** `401 Unauthorized` or `UNAUTHENTICATED` gRPC error. **Causes and fixes:** | Cause | Fix | | --------------- | ---------------------------------------------------------------------------------------------------- | | Malformed key | API keys follow the format `mbt___`. Verify all four segments are present. | | Wrong instance | The instance tag in the key must match the endpoint's instance. Check your `MUBIT_API_KEY`. | | Revoked key | Keys can be revoked via `revokeUserApiKey`. Generate a new key with `rotateUserApiKey`. | | Missing env var | Ensure `MUBIT_API_KEY` is set in your environment or passed to the SDK constructor. | ### Transport and connectivity #### Connection refused **Symptom:** `ECONNREFUSED` or `Connection refused`. | Context | Fix | | ----------------- | ------------------------------------------------------------------------------------ | | Local development | Ensure MuBit is running (`make run-mubit`). Default ports: HTTP 3000, gRPC 50051. | | Hosted endpoint | Check network connectivity to `api.mubit.ai`. Verify no firewall blocking HTTPS/443. | | gRPC transport | Verify `MUBIT_GRPC_ENDPOINT` points to the correct host:port. | #### Transport selection issues **Symptom:** Unexpected transport used, or auto-detection fails. Set `MUBIT_TRANSPORT` explicitly to `"http"` or `"grpc"` instead of relying on `"auto"`. The auto probe can fail in restricted network environments. #### gRPC field names wrong (JS SDK) **Symptom:** Response fields use camelCase instead of snake\_case proto names, or vice versa. **Fix:** Update `@mubit-ai/sdk` to v0.4.1+ which includes the `keepCase` fix. ### Memory and retrieval #### Recall returns empty results | Cause | Fix | | ----------------------------- | ---------------------------------------------------------------------------- | | Data not yet ingested | Ingest is async. Wait for the ingest job to complete (poll `getIngestJob`). | | Wrong `run_id` | `recall` scopes by `run_id`. Ensure you're querying the same run. | | Entry types filter too narrow | Broaden `entry_types` or omit it to search all types. | | Lane mismatch | If using lanes, ensure `lane_filter` matches the `lane` used at ingest time. | #### Context block is empty or too small | Cause | Fix | | -------------------- | ---------------------------------------------------------------- | | Token budget too low | Increase `max_token_budget` (default may be 300). | | No relevant evidence | Ingest more data or broaden the query. | | Wrong mode | Try different `mode` values: `"summary"`, `"full"`, `"compact"`. | | Run has no data | Verify the `run_id` has ingested data with `listActivity`. | #### Lessons not appearing after reflect | Cause | Fix | | -------------------------- | --------------------------------------------------------------------------------- | | No outcomes recorded | Pair `reflect()` with `recordOutcome()` for stronger lesson extraction. | | Too few data points | Reflection needs sufficient evidence. Ingest more interactions before reflecting. | | Step outcomes not included | Pass `include_step_outcomes=True` to include step-level signals. | ### Runs and scoping #### Run scope conflicts **Symptom:** Data from one run appears in another, or data is missing. * Runs are the primary isolation boundary. Each `run_id` scopes its own data. * Linked runs (`linkRun`) share data intentionally. Check if runs are linked. * Use `listRuns()` to see active runs and their relationships. #### Cannot delete a run **Symptom:** `deleteRun` fails or data persists. * Run deletion is permanent and removes all associated data. * Ensure no active ingest jobs are running for the run. * Both control and core planes have `deleteRun` — the control version handles both. ### Lesson promotion #### How lesson promotion works Lessons are promoted through scopes based on recurrence: 1. **Run-scoped** — extracted from a single run's evidence 2. **Session-scoped** — promoted when the same lesson appears across multiple runs 3. **Global** — promoted when recurring across sessions Promotion happens automatically during `reflect()`. You can inspect lesson scope with `listLessons()`. ### Integration packages #### Framework adapter not connecting All integration packages (`mubit-crewai`, `mubit-langgraph`, etc.) use the core SDK internally. Verify: 1. `MUBIT_API_KEY` is set 2. `MUBIT_ENDPOINT` points to a reachable MuBit instance 3. The adapter package version matches your SDK version ### Getting help * SDK issues: [github.com/mubit-ai/ricedb/issues](https://github.com/mubit-ai/ricedb/issues) * API reference: [Control HTTP](/api-reference/control-http) | [Control gRPC](/api-reference/control-grpc) * Configuration: [SDK Configuration Reference](#TODO) import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## Examples / Cookbook Start with the complete multi-SDK implementation first, then use cookbooks for focused patterns. Full implementation page: [Full-fledged support agent](/sdk/full-support-agent) Then apply recipes in this order: 1. `01-quickstart-support-agent.py` -> [Support agent memory loop](/recipes/support-agent-loop) 2. `07-multi-agent-system.py` -> [Multi-agent shared state](/recipes/multi-agent-shared-state) 3. `08-event-driven-agents.py` -> [Building event-driven agents](/recipes/event-driven-agents) 4. `09-branch-what-if.py` -> [Branching what-if simulations](/recipes/branching-memory) 5. `10-stateful-task-tree.py` -> [Stateful task trees](/recipes/stateful-task-trees) ### Framework integration examples Complete example apps with real LLM calls showing MuBit integrated with popular agent frameworks: | Example | Framework | What it shows | | ------------------------------------------------------------- | ------------- | -------------------------------------------------------------- | | [Support Ticket Triage](/sdk/framework-integrations#crewai) | CrewAI | 3-agent crew with handoffs, checkpoints, and outcome recording | | [Code Review Pipeline](/sdk/framework-integrations#langgraph) | LangGraph | StateGraph with conditional reviewer loop and MuBit store | | [Research Assistant](/sdk/framework-integrations#langchain) | LangChain | Multi-turn conversation with cross-session memory | | [Travel Planner](/sdk/framework-integrations#google-adk) | Google ADK | SequentialAgent with Gemini tool calling | | [FAQ Bot](/sdk/framework-integrations#vercel-ai-sdk) | Vercel AI SDK | Middleware auto-injection with knowledge-base tool | See each example's directory for setup and run instructions. ### Failure modes and troubleshooting | Symptom | Root cause | Fix | | ------------------------------------ | --------------------------------------- | ---------------------------------------------------- | | Recipes work alone but fail together | Run identity not carried between stages | Keep deterministic run IDs by role/task | | Progression feels disconnected | Cookbook file order skipped | Follow stage order and reuse the same project folder | ### Next steps * Recheck foundations at [How Mubit works](/sdk/how-mubit-works). * Use route-level references at [Control HTTP reference](/api-reference/control-http). import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## Webhooks Receive events from MuBit when ingest jobs complete, lessons are extracted, or handoffs change state. This page is a stub. Webhook delivery is in private beta. Contact [support@mubit.ai](mailto\:support@mubit.ai) to enable for your instance. ### What can fire a webhook | Event | When it fires | | ------------------ | ------------------------------------------------------------ | | `ingest.completed` | An async `control.ingest` job finishes (success or failure) | | `lesson.extracted` | `reflect()` produces a new lesson | | `lesson.promoted` | A lesson is promoted from `run` → `session` → `global` scope | | `handoff.created` | An agent calls `handoff()` | | `handoff.feedback` | A handoff receives `feedback()` | | `outcome.recorded` | `record_outcome()` is called for a `reference_id` | ### Delivery * HTTPS POST to your configured endpoint with `Content-Type: application/json`. * Body shape: `{ "id": "evt_…", "type": "ingest.completed", "occurred_at": "…", "data": { … } }`. * 5-second send timeout; retried with exponential backoff for up to 24 hours on non-2xx responses. * At-least-once delivery — your handler must be idempotent. Use `id` as the dedupe key. ### Signing Every webhook carries a `MuBit-Signature` header: ``` MuBit-Signature: t=1746547200,v1=8c9f1e… ``` `t` is the Unix timestamp at send time. `v1` is `HMAC-SHA256(signing_secret, f"{t}.{body}")` hex-encoded. Verification: ```python import hmac, hashlib, time def verify(body: bytes, header: str, secret: str, max_skew_s=300) -> bool: parts = dict(p.split("=", 1) for p in header.split(",")) t, v1 = int(parts["t"]), parts["v1"] if abs(time.time() - t) > max_skew_s: return False expected = hmac.new( secret.encode(), f"{t}.".encode() + body, hashlib.sha256 ).hexdigest() return hmac.compare_digest(expected, v1) ``` Reject any request that fails verification. The signing secret is shown once at webhook creation; rotate from the console. ### Configuring an endpoint ```bash curl -X POST https://api.mubit.ai/v2/control/webhooks \ -H "Authorization: Bearer $MUBIT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com/hooks/mubit", "events": ["lesson.extracted", "lesson.promoted"], "description": "Push promoted lessons into our analytics pipeline." }' ``` The response includes the `signing_secret` once. Store it; you cannot retrieve it again. ### Local development For testing without a public URL, use the [MCP webhook bridge](https://github.com/mubit-ai/mubit-mcp-webhook-bridge) or any tunnel (ngrok, cloudflared). Set `events: []` to subscribe to none and pause delivery while debugging without deleting the webhook. ### See also * [Errors](/api-reference/errors) — webhook send failures appear as `webhook.send_failed` activity entries * [Activity & Audit Trail](/sdk/activity-audit) — every webhook send is logged import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## Agno Integration Use MuBit as a persistent memory backend and toolkit for Agno agents. MemoryDb plus an LLM-callable Toolkit. The `mubit-agno` adapter exposes two integration surfaces: * `MemoryDb` — Agno's built-in memory persistence layer. * `Toolkit` — LLM-callable tools (`mubit_remember`, `mubit_recall`, `mubit_reflect`, `mubit_checkpoint`, `mubit_diagnose`). Use `MemoryDb` to give Agno's existing memory system durable storage; use the Toolkit when you want the LLM itself to drive memory operations. ```bash pip install mubit-agno[agno] ``` ### Minimal usage ```python title="agno_minimal.py" from agno.agent import Agent from agno.models.openai import OpenAIChat from agno.memory.v2.memory import Memory from mubit_agno import MubitAgnoMemory mubit = MubitAgnoMemory(api_key="mbt_...", session_id="run-1") agent = Agent( name="Assistant", model=OpenAIChat(id="gpt-4o"), memory=Memory(db=mubit.as_memory_db()), tools=[mubit.as_toolkit()], enable_agentic_memory=True, ) result = agent.run("What do we know about the production database?") # Extended MuBit features mubit.checkpoint("Research done", "Completed infra review") mubit.record_outcome("infra-recall", "success", rationale="Correct recall") mubit.reflect() mubit.archive("SELECT * FROM users WHERE active", "sql_query", labels=["infra"]) ref = mubit.dereference("ref_abc123") ``` ### Full example: Basic agent with persistent memory An Agno agent backed by MuBit. Memories persist across sessions, enabling cross-conversation learning. ```python title="basic.py" import os from agno.agent import Agent from agno.models.openai import OpenAIChat from agno.memory.v2.memory import Memory from mubit_agno import MubitAgnoMemory endpoint = os.environ.get("MUBIT_ENDPOINT", "https://api.mubit.ai") api_key = os.environ["MUBIT_API_KEY"] mubit = MubitAgnoMemory( endpoint=endpoint, api_key=api_key, session_id="basic-example", user_id="demo-user", ) agent = Agent( name="Assistant", model=OpenAIChat(id="gpt-4o"), memory=Memory(db=mubit.as_memory_db()), tools=[mubit.as_toolkit()], enable_agentic_memory=True, instructions=[ "You have access to MuBit memory tools.", "Use mubit_remember to store important information.", "Use mubit_recall to search for relevant memories.", ], ) # Run 1: store knowledge agent.run( "Remember that our production database is PostgreSQL 16 " "running on AWS RDS in us-east-1, and we use Redis for caching.", session_id="session-1", ) mubit.checkpoint("Initial learning", "Stored infrastructure facts") # Run 2: recall knowledge (cross-session) agent.run("What database do we use in production?", session_id="session-2") mubit.record_outcome( "infrastructure-recall", "success", rationale="Agent correctly recalled database details from memory", ) ``` ### Full example: Multi-agent coordination Two Agno agents — a researcher and a reviewer — share a MuBit run. The researcher hands off findings; the reviewer evaluates, surfaces strategies, and records the outcome. ```python title="multi_agent.py" import os from agno.agent import Agent from agno.models.openai import OpenAIChat from agno.memory.v2.memory import Memory from mubit_agno import MubitAgnoMemory endpoint = os.environ.get("MUBIT_ENDPOINT", "https://api.mubit.ai") api_key = os.environ["MUBIT_API_KEY"] SESSION = "agno-coord-1" researcher_mem = MubitAgnoMemory( endpoint=endpoint, api_key=api_key, session_id=SESSION, agent_id="researcher", ) reviewer_mem = MubitAgnoMemory( endpoint=endpoint, api_key=api_key, session_id=SESSION, agent_id="reviewer", ) # Register both agents so handoffs and scopes are explicit for mem, role, scopes in [ (researcher_mem, "solution-researcher", ["fact", "lesson"]), (reviewer_mem, "quality-reviewer", ["fact", "lesson", "rule"]), ]: mem.register_agent(role=role, read_scopes=scopes, write_scopes=scopes) researcher = Agent( name="Researcher", model=OpenAIChat(id="gpt-4o-mini"), memory=Memory(db=researcher_mem.as_memory_db()), tools=[researcher_mem.as_toolkit()], enable_agentic_memory=True, ) reviewer = Agent( name="Reviewer", model=OpenAIChat(id="gpt-4o-mini"), memory=Memory(db=reviewer_mem.as_memory_db()), tools=[reviewer_mem.as_toolkit()], enable_agentic_memory=True, ) # Researcher produces findings and hands off to reviewer findings = researcher.run( "Investigate why tenant billing discrepancies spiked this week. " "Store the top 3 causes as facts." ) researcher_mem.handoff( from_agent_id="researcher", to_agent_id="reviewer", content=str(findings.content)[:1000], requested_action="review", ) # Reviewer consumes the handoff, evaluates, feeds back, surfaces strategies review = reviewer.run( "Review the researcher's billing findings. Call out anything weak and " "use mubit_strategies to see what has worked before." ) reviewer_mem.feedback( handoff_id=findings.run_id, verdict="approve", comments=str(review.content)[:1000], from_agent_id="reviewer", ) # Close the loop: reflect and record outcome reviewer_mem.reflect() reviewer_mem.record_outcome( reference_id="billing-discrepancy-review", outcome="success", rationale="Reviewer confirmed findings matched stored rules.", ) strategies = reviewer_mem.surface_strategies(lesson_types=["success"], max_strategies=3) print(f"Reusable strategies: {len(strategies.get('strategies', []))}") ``` ### Extended MuBit features `MubitAgnoMemory` exposes the full MAS surface as instance methods: ```python mubit.register_agent(role="...", read_scopes=[...], write_scopes=[...]) mubit.handoff(from_agent_id="...", to_agent_id="...", content="...", requested_action="review") mubit.feedback(handoff_id="...", verdict="approve", from_agent_id="...") mubit.checkpoint(snapshot="...", label="...") mubit.record_outcome(reference_id="...", outcome="success", rationale="...") mubit.reflect() mubit.surface_strategies(lesson_types=["success"], max_strategies=3) mubit.diagnose(query="...") mubit.archive(content="...", artifact_kind="...", labels=[...]) mubit.dereference(reference_id="...") mubit.memory_health() ``` ### Gotchas * **The Toolkit and the MemoryDb are independent.** Pass both to `Agent(...)` if you want both Agno's automatic memory and the LLM-callable tools. * **`enable_agentic_memory=True`** enables Agno's read-write memory loop. Without it the LLM can't call the tools. * **`session_id` is the MuBit scope.** Reuse it across `agent.run()` calls to share memory. ### Version compatibility | `mubit-agno` | `mubit-sdk` | `agno` | | ------------ | ----------- | ---------- | | `0.5.x` | `>= 0.6.0` | `>= 1.0.0` | import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## CrewAI Integration Route CrewAI's unified Memory system through MuBit. Agent observations persist across runs and crews. The `mubit-crewai` adapter exposes a `StorageBackend` that CrewAI's unified Memory accepts. Once wired up, every agent observation in every crew run is queryable across crews — without changing how the crew is built. ```bash pip install mubit-crewai[crewai] ``` ### Minimal usage ```python title="crewai_minimal.py" from mubit_crewai import MubitCrewMemory from crewai import Crew, Process memory = MubitCrewMemory(api_key="mbt_...", session_id="crew-run-1") crew = Crew( agents=[researcher, writer], tasks=[research_task, write_task], process=Process.sequential, memory=memory.as_crew_memory(), ) result = crew.kickoff(inputs={"topic": "AI safety"}) # Extended MuBit features memory.checkpoint("Research phase complete") memory.record_outcome("task-1", "success") memory.handoff("researcher", "writer", "Here are findings", requested_action="execute") ``` ### Full example: Support ticket triage crew A 3-agent crew (classifier, researcher, responder) that processes customer support tickets. Each agent learns from previous triage outcomes through MuBit memory. ```python title="support_triage/main.py" from crewai import Agent, Task, Crew, Process from mubit_crewai import MubitCrewMemory memory = MubitCrewMemory( endpoint="https://api.mubit.ai", api_key="mbt_...", session_id="triage-001", agent_id="crewai-triage", ) # Register agents for MAS coordination for agent_def in [ {"agent_id": "classifier", "role": "ticket-classifier"}, {"agent_id": "researcher", "role": "solution-researcher"}, {"agent_id": "responder", "role": "response-drafter"}, ]: memory.register_agent(**agent_def) classifier = Agent( role="Support Ticket Classifier", goal="Classify tickets by severity and category", backstory="Experienced support lead who identifies severity and escalation triggers.", llm="openai/gpt-4o-mini", ) researcher = Agent( role="Solution Researcher", goal="Find relevant past solutions from MuBit memory", backstory="Knowledge specialist who searches for similar tickets and resolution patterns.", llm="openai/gpt-4o-mini", ) responder = Agent( role="Customer Response Drafter", goal="Draft empathetic, actionable replies", backstory="Senior success agent who turns frustrated customers into advocates.", llm="openai/gpt-4o-mini", ) classify_task = Task( description="Classify this ticket: {ticket}", expected_output="Severity, category, key issues, escalation flags.", agent=classifier, ) research_task = Task( description="Research solutions for: {ticket}", expected_output="Past cases, known solutions, systemic patterns.", agent=researcher, ) respond_task = Task( description="Draft a response for: {ticket}", expected_output="Professional, empathetic customer response.", agent=responder, ) crew = Crew( agents=[classifier, researcher, responder], tasks=[classify_task, research_task, respond_task], process=Process.sequential, memory=memory.as_crew_memory(), ) result = crew.kickoff(inputs={"ticket": "Duplicate charge on Pro subscription..."}) # Post-run: handoffs, checkpoint, outcome memory.handoff("classifier", "researcher", "Classification complete.", requested_action="continue") memory.handoff("researcher", "responder", "Research complete.", requested_action="execute") memory.checkpoint(snapshot="Triage complete.", label="triage-complete") memory.record_outcome(reference_id="triage-001", outcome="success", rationale="Ticket resolved.") ``` ### Extended MuBit features `MubitCrewMemory` exposes the full MAS surface alongside the framework hook: ```python memory.register_agent(agent_id="...", role="...", read_scopes=[...], write_scopes=[...]) memory.handoff(from_agent_id="...", to_agent_id="...", content="...", requested_action="review") memory.feedback(handoff_id="...", verdict="approve", from_agent_id="...") memory.checkpoint(snapshot="...", label="...") memory.record_outcome(reference_id="...", outcome="success", rationale="...") memory.reflect() # extract lessons from the run memory.surface_strategies() # cluster recurring success lessons memory.diagnose(query="...") # failure-path lessons for debugging memory.archive(content="...", artifact_kind="...") # exact recovery memory.dereference(reference_id="...") ``` See [SDK helpers](/sdk/sdk-helpers) for the meaning of each method. ### Gotchas * **Memory scope is per `session_id`.** A new `session_id` is a fresh crew memory; reuse the id to share memory across kickoffs. * **CrewAI's built-in short-term and long-term memories are both routed through MuBit.** No need to configure them separately. * **Sequential vs. hierarchical processes** both work; `Process.hierarchical` benefits especially from `register_agent()` so handoffs are queryable. ### Version compatibility | `mubit-crewai` | `mubit-sdk` | `crewai` | | -------------- | ----------- | --------- | | `0.5.x` | `>= 0.6.0` | `>= 0.86` | When upgrading the core SDK, also upgrade `mubit-crewai` to a matching minor. import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## Google ADK Integration Plug MuBit into ADK's Runner as a BaseMemoryService. Session events auto-ingest; memory search enriches agent context. The `mubit-adk` adapter implements ADK's `BaseMemoryService`. Pass it to `Runner(memory_service=…)` and every session event is automatically ingested into MuBit; memory search enriches agent context on the next turn. ```bash pip install mubit-adk[adk] ``` ### Minimal usage ```python title="google_adk_minimal.py" from mubit_adk import MubitMemoryService from google.adk.agents import LlmAgent, SequentialAgent from google.adk.runners import Runner from google.adk.sessions import InMemorySessionService memory_service = MubitMemoryService(api_key="mbt_...") agent = SequentialAgent(name="coordinator", sub_agents=[flight_agent, hotel_agent, planner_agent]) runner = Runner( agent=agent, app_name="travel", session_service=InMemorySessionService(), memory_service=memory_service, ) # MAS extensions await memory_service.checkpoint(app_name="travel", user_id="user-1", snapshot="Plan complete") await memory_service.register_agent(user_id="user-1", agent_id="planner", role="itinerary") ``` ### Full example: Travel planner A `SequentialAgent` with Gemini, tool calling, and MAS coordination. Three agents (flight finder, hotel finder, itinerary planner) collaborate through MuBit memory. ```python title="travel_planner/main.py" from google.adk.agents import LlmAgent, SequentialAgent from google.adk.runners import Runner from google.adk.sessions import InMemorySessionService from mubit_adk import MubitMemoryService mubit_memory = MubitMemoryService(endpoint="https://api.mubit.ai", api_key="mbt_...") flight_finder = LlmAgent( name="flight_finder", model="gemini-3.1-flash-lite-preview", description="Finds the best flights", instruction="Search for flights and recommend the best option.", tools=[search_flights], output_key="flight_results", ) hotel_finder = LlmAgent( name="hotel_finder", model="gemini-3.1-flash-lite-preview", description="Finds accommodations", instruction="Search for hotels and recommend the best option.", tools=[search_hotels], output_key="hotel_results", ) itinerary_planner = LlmAgent( name="itinerary_planner", model="gemini-3.1-flash-lite-preview", description="Creates day-by-day itinerary", instruction="Combine flight and hotel info into a complete travel plan.", output_key="final_itinerary", ) coordinator = SequentialAgent( name="travel_coordinator", sub_agents=[flight_finder, hotel_finder, itinerary_planner], ) runner = Runner( agent=coordinator, app_name="travel", session_service=InMemorySessionService(), memory_service=mubit_memory, ) # After the pipeline runs, record MuBit operations await mubit_memory.register_agent(user_id="user-1", agent_id="flight_finder", role="finder") await mubit_memory.checkpoint(app_name="travel", user_id="user-1", snapshot="Plan complete") await mubit_memory.record_outcome( user_id="user-1", reference_id="travel-001", outcome="success", rationale="Itinerary confirmed by user.", ) ``` ### Extended MuBit features `MubitMemoryService` is async — every method returns a coroutine. ```python await memory_service.register_agent(user_id="...", agent_id="...", role="...", read_scopes=[...], write_scopes=[...]) await memory_service.handoff(user_id="...", from_agent_id="...", to_agent_id="...", task_id="...", content="...", requested_action="review") await memory_service.feedback(user_id="...", handoff_id="...", verdict="approve", from_agent_id="...") await memory_service.checkpoint(app_name="...", user_id="...", snapshot="...", label="...") await memory_service.record_outcome(user_id="...", reference_id="...", outcome="success", rationale="...") await memory_service.reflect(user_id="...") await memory_service.surface_strategies(user_id="...") await memory_service.diagnose(user_id="...", query="...") await memory_service.archive(user_id="...", content="...", artifact_kind="...") await memory_service.dereference(user_id="...", reference_id="...") ``` ### Gotchas * **`app_name` + `user_id`** form the namespace ADK uses internally; the adapter joins them into a MuBit session id. * **Auto-ingestion is opt-out, not opt-in.** Set `auto_ingest_events=False` on `MubitMemoryService(...)` if you want to capture only what you call manually. * **Gemini streaming responses** are captured incrementally; the assembled output is what gets stored. ### Version compatibility | `mubit-adk` | `mubit-sdk` | `google-adk` | | ----------- | ----------- | ------------ | | `0.5.x` | `>= 0.6.0` | `>= 1.0.0` | import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## LangChain Integration Drop-in BaseMemory for any LangChain chain. Cross-session memory works automatically through MuBit's semantic retrieval. `mubit-langchain` provides `MubitChatMemory`, a `BaseMemory` subclass that any LangChain chain accepts. It auto-loads context before each call and saves the interaction after; cross-session retrieval works through MuBit's semantic recall, not LangChain's local buffer. ```bash pip install mubit-langchain[langchain] ``` ### Minimal usage ```python title="langchain_minimal.py" from mubit_langchain import MubitChatMemory from langchain_openai import ChatOpenAI memory = MubitChatMemory(api_key="mbt_...", session_id="chat-1") llm = ChatOpenAI(model="gpt-4o-mini") # Manual loop — useful for understanding the contract context = memory.load_memory_variables({"input": "What happened yesterday?"}) # build messages with context["history"], call LLM memory.save_context({"input": question}, {"output": response}) ``` ### Full example: Research assistant with cross-session memory A multi-turn research conversation across two sessions. Session 2 retrieves relevant facts from Session 1. ```python title="research_assistant/main.py" from langchain_openai import ChatOpenAI from langchain_core.messages import HumanMessage, SystemMessage from mubit_langchain import MubitChatMemory llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.3) # Session 1: learn about a topic memory_s1 = MubitChatMemory( api_key="mbt_...", session_id="research-s1", agent_id="research-assistant", ) for question in [ "What caused the 2008 financial crisis?", "How did subprime mortgages contribute?", ]: context = memory_s1.load_memory_variables({"input": question}) messages = [SystemMessage(content="You are a research assistant.")] if context.get("history"): messages.extend(context["history"]) messages.append(HumanMessage(content=question)) response = llm.invoke(messages) memory_s1.save_context({"input": question}, {"output": response.content}) # Session 2: new session, cross-session memory works automatically memory_s2 = MubitChatMemory( api_key="mbt_...", session_id="research-s2", agent_id="research-assistant", ) context = memory_s2.load_memory_variables({"input": "What about crisis prevention?"}) # Session 2's context now includes relevant facts from Session 1. ``` ### Extended MuBit features `MubitChatMemory` exposes the MAS surface alongside the `BaseMemory` contract: ```python memory.register_agent(agent_id="...", role="...", read_scopes=[...], write_scopes=[...]) memory.handoff(from_agent_id="...", to_agent_id="...", content="...", requested_action="review") memory.feedback(handoff_id="...", verdict="approve", from_agent_id="...") memory.checkpoint(snapshot="...", label="...") memory.record_outcome(reference_id="...", outcome="success", rationale="...") memory.reflect() memory.surface_strategies() memory.diagnose(query="...") memory.archive(content="...", artifact_kind="...") memory.dereference(reference_id="...") memory.get_context(query="...", mode="summary", max_token_budget=300) ``` ### Gotchas * **`memory_key` defaults to `"history"`.** If your chain expects a different key, pass `MubitChatMemory(memory_key="chat_history", ...)`. * **`return_messages=True`** is the default — `context["history"]` is a list of `BaseMessage` objects, not a single string. * **Session vs. user memory:** for cross-session recall keyed by user, pass `user_id="…"` to `MubitChatMemory` and store with `intent="lesson"`, `lesson_scope="global"`. See [SDK helpers → cross-session recall](/sdk/sdk-helpers#cross-session-recall). * **`load_memory_variables()` makes a network call.** Don't call it inside tight loops. ### Version compatibility | `mubit-langchain` | `mubit-sdk` | `langchain` | | ----------------- | ----------- | ----------- | | `0.5.x` | `>= 0.6.0` | `>= 0.3.0` | import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## LangGraph Integration Use MuBit as a persistent BaseStore for LangGraph StateGraphs. Each node reads or writes through PutOp / SearchOp. The `mubit-langgraph` adapter implements LangGraph's `BaseStore` interface. Pass it to `graph.compile(store=...)` and your graph nodes can read or write memory through the standard `PutOp` / `SearchOp` ops without any LangGraph-specific MuBit knowledge. ```bash pip install mubit-langgraph[langgraph] ``` ### Minimal usage ```python title="langgraph_minimal.py" from mubit_langgraph import MubitStore from langgraph.graph import StateGraph from langgraph.store.base import PutOp, SearchOp store = MubitStore(api_key="mbt_...") NS = ("memories", "user-1", "session-1") # In graph nodes, use the store directly store.batch([PutOp(namespace=NS, key="finding-1", value={"text": "...", "intent": "lesson"})]) results = store.batch([SearchOp(namespace_prefix=NS, query="security issues", limit=5)]) # MAS extensions store.register_agent(NS, agent_id="reviewer", role="code-reviewer") store.checkpoint(NS, snapshot="Review complete") store.handoff(NS, from_agent_id="planner", to_agent_id="reviewer", content="...", requested_action="review") graph.compile(store=store) ``` ### Full example: Code review pipeline A `StateGraph` with planner, reviewer loop, and summarizer. The MuBit store persists findings across steps and across sessions. ```python title="code_review/main.py" from langgraph.graph import StateGraph, START, END from langgraph.store.base import PutOp, SearchOp from mubit_langgraph import MubitStore store = MubitStore(endpoint="https://api.mubit.ai", api_key="mbt_...") NAMESPACE = ("memories", "code-reviewer", "review-session") # Register agents for agent_id, role in [ ("planner", "review-planner"), ("reviewer", "item-reviewer"), ("summarizer", "review-summarizer"), ]: store.register_agent(NAMESPACE, agent_id=agent_id, role=role) def planner_node(state): past = store.batch([SearchOp(namespace_prefix=NAMESPACE, query="code review checklist", limit=3)])[0] # ... LLM call to generate checklist ... store.checkpoint(NAMESPACE, snapshot=f"Checklist created with {len(checklist)} items") return {"checklist": checklist, "current_idx": 0, "findings": []} def reviewer_node(state): item = state["checklist"][state["current_idx"]] # ... LLM call to evaluate item ... store.batch([PutOp( namespace=NAMESPACE, key=f"finding-{state['current_idx']}", value={"text": finding, "intent": "lesson"}, )]) return {"findings": state["findings"] + [finding], "current_idx": state["current_idx"] + 1} def summarizer_node(state): context = store.get_context(NAMESPACE, query="code review findings", max_token_budget=4096) # ... LLM call to synthesize final review ... store.record_outcome(NAMESPACE, reference_id="review-001", outcome="success", rationale="Review completed.") return {"final_review": review} graph = StateGraph(ReviewState) graph.add_node("planner", planner_node) graph.add_node("reviewer", reviewer_node) graph.add_node("summarizer", summarizer_node) graph.add_edge(START, "planner") graph.add_edge("planner", "reviewer") graph.add_conditional_edges("reviewer", should_continue, {"reviewer": "reviewer", "summarizer": "summarizer"}) graph.add_edge("summarizer", END) result = graph.compile().invoke({ "code_diff": "...", "checklist": [], "current_idx": 0, "findings": [], "final_review": "", }) ``` ### Extended MuBit features `MubitStore` exposes the full MAS surface. Every method takes the namespace as its first positional argument: ```python store.register_agent(NS, agent_id="...", role="...", read_scopes=[...], write_scopes=[...]) store.handoff(NS, from_agent_id="...", to_agent_id="...", content="...", requested_action="review") store.feedback(NS, handoff_id="...", verdict="approve", from_agent_id="...") store.checkpoint(NS, snapshot="...", label="...") store.record_outcome(NS, reference_id="...", outcome="success", rationale="...") store.reflect(NS) store.surface_strategies(NS) store.diagnose(NS, query="...") store.archive(NS, content="...", artifact_kind="...") store.dereference(NS, reference_id="...") store.get_context(NS, query="...", max_token_budget=...) ``` ### Gotchas * **Namespace is the source of truth for scope.** Use a stable tuple shape across nodes (`(memories, user_id, session_id)`) so `SearchOp` finds writes from earlier nodes. * **`PutOp.value` must be a dict.** Cast strings before storing — the adapter rejects raw strings. * **`SearchOp` returns a list of `Item` objects.** Each `Item.value` is the dict you stored. * **JS adapter:** `npm install @mubit-ai/langgraph` provides the same surface async, with `camelCase` method names. ### Version compatibility | `mubit-langgraph` | `mubit-sdk` | `langgraph` | | ----------------- | ----------- | ----------- | | `0.5.x` | `>= 0.6.0` | `>= 0.2.40` | import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## LlamaIndex Integration Use MuBit as a chat-memory store and a vector store for LlamaIndex indexes. Two surfaces, one memory pool. The `mubit-llama-index` adapter exposes two surfaces: * `MubitChatStore` — implements `BaseChatStore` for conversation history. * `MubitVectorStore` — implements `VectorStore` for retrieved documents. Use either independently, or both together for a chat engine that grounds answers in indexed docs and remembers every exchange. ```bash pip install mubit-llama-index[llama-index] ``` ### Minimal usage ```python title="llamaindex_minimal.py" from mubit_llama_index import MubitChatStore, MubitVectorStore from llama_index.core import VectorStoreIndex, StorageContext from llama_index.core.memory import ChatMemoryBuffer chat_store = MubitChatStore(api_key="mbt_...", session_id="session-1") memory = ChatMemoryBuffer.from_defaults(chat_store=chat_store, chat_store_key="user-1") vector_store = MubitVectorStore(api_key="mbt_...", session_id="session-1") storage_context = StorageContext.from_defaults(vector_store=vector_store) index = VectorStoreIndex.from_documents(documents, storage_context=storage_context) # MAS extensions are available through the underlying client chat_store.client.checkpoint(session_id="session-1", context_snapshot="Index loaded") chat_store.client.register_agent(session_id="session-1", agent_id="retriever", role="rag") ``` ### Full example: Document-grounded chat with persistent memory A chat engine that answers from indexed docs and remembers every exchange. Session 2 benefits from both the index and the prior conversation. ```python title="llama_rag/main.py" import os from llama_index.core import ( SimpleDirectoryReader, VectorStoreIndex, StorageContext, Settings, ) from llama_index.core.memory import ChatMemoryBuffer from llama_index.llms.openai import OpenAI from mubit_llama_index import MubitChatStore, MubitVectorStore endpoint = os.environ.get("MUBIT_ENDPOINT", "https://api.mubit.ai") api_key = os.environ["MUBIT_API_KEY"] Settings.llm = OpenAI(model="gpt-4o-mini", temperature=0.2) chat_store = MubitChatStore(endpoint=endpoint, api_key=api_key, session_id="llama-chat-s1") vector_store = MubitVectorStore(endpoint=endpoint, api_key=api_key, session_id="llama-chat-s1") documents = SimpleDirectoryReader("./docs").load_data() storage_context = StorageContext.from_defaults(vector_store=vector_store) index = VectorStoreIndex.from_documents(documents, storage_context=storage_context) memory = ChatMemoryBuffer.from_defaults( chat_store=chat_store, chat_store_key="user-1", token_limit=2048, ) chat_engine = index.as_chat_engine(chat_mode="context", memory=memory) # Session 1: a few turns, all persisted to MuBit for question in [ "What's our deployment process?", "Who approves production releases?", ]: response = chat_engine.chat(question) print(f"Q: {question}\nA: {response}\n") # Record outcome and checkpoint via the underlying MubitControlClient chat_store.client.checkpoint( session_id="llama-chat-s1", context_snapshot="Deployment docs session complete", label="docs-rag-s1", ) chat_store.client.record_outcome( session_id="llama-chat-s1", reference_id="llama-rag-turn-2", outcome="success", rationale="Retrieval surfaced approval workflow correctly.", ) # Session 2: new session, same chat_store_key — conversation history persists chat_store_s2 = MubitChatStore(endpoint=endpoint, api_key=api_key, session_id="llama-chat-s2") memory_s2 = ChatMemoryBuffer.from_defaults( chat_store=chat_store_s2, chat_store_key="user-1", # same key → cross-session continuity token_limit=2048, ) chat_engine_s2 = index.as_chat_engine(chat_mode="context", memory=memory_s2) print(chat_engine_s2.chat("Remind me — who approves prod releases?")) ``` ### Extended MuBit features LlamaIndex's contract is narrow, so MuBit's MAS surface is exposed via the underlying client object rather than on the chat/vector store directly: ```python client = chat_store.client # or vector_store.client client.register_agent(session_id="...", agent_id="...", role="...", read_scopes=[...], write_scopes=[...]) client.handoff(session_id="...", from_agent_id="...", to_agent_id="...", task_id="...", content="...", requested_action="review") client.feedback(session_id="...", handoff_id="...", verdict="approve", from_agent_id="...") client.checkpoint(session_id="...", context_snapshot="...", label="...") client.record_outcome(session_id="...", reference_id="...", outcome="success", rationale="...") client.reflect(session_id="...") client.surface_strategies(session_id="...") client.diagnose(session_id="...", query="...") client.archive(session_id="...", content="...", artifact_kind="...") client.dereference(session_id="...", reference_id="...") ``` ### Gotchas * **`chat_store_key` controls cross-session continuity.** Same key + different `session_id` = same conversation history surfaced. * **The vector store and chat store are independent.** Sharing a `session_id` is a convention, not a coupling. * **Async embeddings:** `MubitVectorStore` writes are async-batched. If you need a synchronous read immediately after insert, call `vector_store.flush()`. ### Version compatibility | `mubit-llama-index` | `mubit-sdk` | `llama-index` | | ------------------- | ----------- | ------------- | | `0.5.x` | `>= 0.6.0` | `>= 0.11.0` | import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## MCP Server Expose MuBit as 15 tools over the Model Context Protocol stdio transport. Usable from Claude Desktop, Cursor, and any MCP client. `@mubit-ai/mcp` is a Model Context Protocol server that exposes MuBit as a tool surface. Drop it into any MCP-compatible agent (Claude Desktop, Cursor, custom agents) and the agent can read or write memory through standard MCP tool calls — no MuBit-specific code in the agent itself. ```bash npm install @mubit-ai/mcp ``` ### Minimal usage ```bash title="mcp_minimal.sh" npx @mubit-ai/mcp --api-key $MUBIT_API_KEY --endpoint https://api.mubit.ai ``` The server speaks MCP over stdio. Configure your MCP client to spawn it with the same flags. #### Claude Desktop config ```json title="~/Library/Application Support/Claude/claude_desktop_config.json" { "mcpServers": { "mubit": { "command": "npx", "args": ["-y", "@mubit-ai/mcp", "--api-key", "mbt_...", "--endpoint", "https://api.mubit.ai"] } } } ``` #### Cursor config ```json title=".cursor/mcp.json" { "mcpServers": { "mubit": { "command": "npx", "args": ["-y", "@mubit-ai/mcp"], "env": { "MUBIT_API_KEY": "mbt_...", "MUBIT_ENDPOINT": "https://api.mubit.ai" } } } } ``` ### Full tool surface **Core memory:** `mubit_remember`, `mubit_recall`, `mubit_context`, `mubit_archive`, `mubit_dereference`, `mubit_reflect`, `mubit_lessons`, `mubit_forget`, `mubit_status` **MAS and learning loop:** `mubit_checkpoint`, `mubit_outcome`, `mubit_strategies`, `mubit_register_agent`, `mubit_list_agents`, `mubit_handoff`, `mubit_feedback` **Observability:** `mubit_memory_health`, `mubit_diagnose` ### Common flows ```text Checkpoint + reflection: mubit_checkpoint → mubit_reflect → mubit_lessons MAS coordination: mubit_register_agent → mubit_handoff → mubit_feedback Failure diagnosis: mubit_diagnose → mubit_memory_health → mubit_recall RAG with exact recovery: mubit_archive → mubit_dereference ``` ### Per-tool input schemas Each tool accepts the same arguments as the corresponding SDK helper. For example, `mubit_remember` takes `session_id`, `agent_id`, `content`, `intent`, `metadata`, etc. Run `--help` or inspect `tools/list` from your MCP client for the full schemas. ### Gotchas * **Stdio only.** SSE transport is on the roadmap; stdio is the only option today. * **One server, one instance.** Each spawn binds to a single MuBit instance (the one in your `MUBIT_API_KEY`). Use multiple `mcpServers` entries if you need multiple instances. * **Idempotency keys** are auto-derived from tool arguments. Re-issuing the same call within 24 hours returns the original `record_id` rather than creating a duplicate. * **`--allow-forget`** flag is required to expose `mubit_forget`; off by default to prevent accidental deletes. ### Version compatibility | `@mubit-ai/mcp` | `mubit-sdk` (Node) | `@modelcontextprotocol/sdk` | | --------------- | ------------------ | --------------------------- | | `0.5.x` | `>= 0.6.0` | `>= 0.6.0` | import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## Vercel AI SDK Integration Middleware that wraps any AI SDK model with automatic memory injection and interaction capture. `@mubit-ai/ai-sdk` provides `mubitMemoryMiddleware`, a middleware factory you pass to Vercel AI SDK's `wrapLanguageModel()`. The wrapped model auto-injects relevant lessons before each call, captures the interaction, and reflects on session end. ```bash npm install @mubit-ai/ai-sdk ``` ### Minimal usage ```js title="vercel_ai_sdk_minimal.mjs" import { wrapLanguageModel, generateText } from "ai"; import { openai } from "@ai-sdk/openai"; import { mubitMemoryMiddleware } from "@mubit-ai/ai-sdk"; const model = wrapLanguageModel({ model: openai("gpt-4o"), middleware: mubitMemoryMiddleware({ apiKey: process.env.MUBIT_API_KEY, sessionId: "session-1", agentId: "support-agent", }), }); const { text } = await generateText({ model, prompt: "How do I reset my password?", }); // Lessons auto-injected before call; interaction auto-captured after. ``` ### Full example: FAQ bot with cross-session learning A multi-session FAQ bot with a knowledge-base tool. Session 2 benefits from a lesson captured in Session 1. ```js title="faq_bot/index.mjs" import { generateText, tool, wrapLanguageModel } from "ai"; import { openai } from "@ai-sdk/openai"; import { z } from "zod"; import { mubitMemoryMiddleware, MubitClient } from "@mubit-ai/ai-sdk"; const mubitClient = new MubitClient({ apiKey: process.env.MUBIT_API_KEY }); const lookupKnowledgeBase = tool({ description: "Search the knowledge base for help articles", parameters: z.object({ query: z.string() }), execute: async ({ query }) => findBestArticle(query), }); // Session 1: answer a couple of FAQ questions const model1 = wrapLanguageModel({ model: openai("gpt-4o-mini"), middleware: mubitMemoryMiddleware({ sessionId: "faq-session-1", agentId: "faq-bot", mubitClient, }), }); for (const question of [ "How do I reset my password?", "What are system requirements?", ]) { const { text } = await generateText({ model: model1, tools: { lookupKnowledgeBase }, maxSteps: 3, prompt: question, }); console.log(`Q: ${question}\nA: ${text}`); } // Ingest a lesson between sessions await mubitClient.ingest({ session_id: "faq-session-1", text: "Users who ask about password reset often also need 2FA help.", intent: "lesson", }); // Session 2: new session benefits from memory const model2 = wrapLanguageModel({ model: openai("gpt-4o-mini"), middleware: mubitMemoryMiddleware({ sessionId: "faq-session-2", agentId: "faq-bot", mubitClient, }), }); const { text } = await generateText({ model: model2, tools: { lookupKnowledgeBase }, maxSteps: 3, prompt: "I'm having trouble logging in", }); // Response references password / 2FA from Session 1. ``` ### Extended MuBit features The middleware focuses on inject + capture. The full MAS surface is on the `MubitClient` you pass in (or that the middleware constructs internally — pass your own to share it across middlewares): ```js await mubitClient.registerAgent({ sessionId, agentId, role, readScopes, writeScopes }); await mubitClient.handoff({ sessionId, taskId, fromAgentId, toAgentId, content, requestedAction }); await mubitClient.feedback({ sessionId, handoffId, verdict, fromAgentId }); await mubitClient.checkpoint({ sessionId, snapshot, label }); await mubitClient.recordOutcome({ sessionId, referenceId, outcome, rationale }); await mubitClient.reflect({ sessionId }); await mubitClient.surfaceStrategies({ sessionId, lessonTypes, maxStrategies }); await mubitClient.diagnose({ sessionId, query }); await mubitClient.archive({ sessionId, content, artifactKind }); await mubitClient.dereference({ sessionId, referenceId }); await mubitClient.getContext({ sessionId, query, mode, maxTokenBudget }); ``` ### Gotchas * **`maxSteps`** matters: tool-calling roundtrips count as separate model calls. The middleware injects lessons for each. * **Streaming responses** (`streamText`) are supported; the assembled output is captured on stream completion. * **Reflection on session end** is opt-in. Pass `autoReflect: true` to the middleware, or call `mubitClient.reflect(...)` yourself when the session ends. * **No CommonJS:** `@mubit-ai/ai-sdk` ships ESM-only. ### Version compatibility | `@mubit-ai/ai-sdk` | `mubit-sdk` (Node) | `ai` | | ------------------ | ------------------ | ---------- | | `0.5.x` | `>= 0.6.0` | `>= 4.0.0` | import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## Branching Memory (What-If Simulations) Use checkpoints and core session branches for reversible what-if exploration without polluting the main execution path. When one task has multiple candidate strategies, preserve the main path and branch only the temporary exploratory state. Current MuBit supports this with two complementary tools: * `checkpoint()` on the control plane for the durable main-path snapshot * core session create / commit / drop for reversible what-if branches ### Decision model | Requirement | Primitive | | ----------------------------------------------------------------------- | ----------------------------------------------------------------- | | Preserve the current durable task state before compaction or divergence | `checkpoint()` | | Try a temporary branch and discard it later | `core.create_session`, `core.commit_session`, `core.drop_session` | | Reload a saved scratch path | `core.snapshot_session`, `core.load_session` | ### Minimal implementation example ```python title="branch_what_if.py" from mubit import Client import os run_id = "claims:planner:claim-42" client = Client( endpoint=os.getenv("MUBIT_ENDPOINT", "https://api.mubit.ai"), api_key=os.environ["MUBIT_API_KEY"], run_id=run_id, transport="http", ) client.checkpoint( session_id=run_id, label="before-strategy-branch", context_snapshot="Planner has two settlement strategies to compare.", metadata={"stage": "what-if"}, ) base = client.core.create_session({}) branch = client.core.create_session({"parent_session_id": base["session_id"]}) branch_id = branch["session_id"] snapshot = client.core.snapshot_session({"id": branch_id, "session_id": branch_id}) client.core.load_session({"session_id": branch_id, "snapshot": snapshot.get("snapshot")}) client.core.drop_session({"id": branch_id, "session_id": branch_id}) ``` ```ts title="branch_what_if.ts" import { Client } from "@mubit-ai/sdk"; const runId = "claims:planner:claim-42"; const client = new Client({ endpoint: process.env.MUBIT_ENDPOINT ?? "https://api.mubit.ai", api_key: process.env.MUBIT_API_KEY, run_id: runId, transport: "http", }); await client.checkpoint({ session_id: runId, label: "before-strategy-branch", context_snapshot: "Planner has two settlement strategies to compare.", metadata: { stage: "what-if" }, }); const base = await client.core.createSession({}); const branch = await client.core.createSession({ parent_session_id: base.session_id }); const branchId = branch.session_id; const snapshot = await client.core.snapshotSession({ id: branchId, session_id: branchId }); await client.core.loadSession({ session_id: branchId, snapshot: snapshot.snapshot }); await client.core.dropSession({ id: branchId, session_id: branchId }); ``` ### Failure modes and troubleshooting | Symptom | Root cause | Fix | | --------------------------------------------- | ---------------------------------------- | ---------------------------------------- | | Main path gets overwritten by experimentation | No durable checkpoint before branching | Always checkpoint the primary path first | | Scratch changes vanish too early | Branch was dropped before review | Snapshot or commit only after inspection | | Compaction loses the decision context | Branching happened without checkpointing | Save a checkpoint before diverging | ### Next steps * Apply the durable path in [Sessions and branching](/sdk/sessions-and-branching). * Combine branching with planner state at [Stateful task trees](/recipes/stateful-task-trees). import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## Building Event-Driven Agents Use control events to react to memory changes, checkpoints, outcomes, and coordination artifacts instead of relying only on polling loops. Polling is acceptable for simple loops, but event-driven coordination is better when planners and workers need to react to new memory, handoffs, feedback, checkpoints, or outcome updates. The event stream is still a low-level control-plane surface. Keep the public integration model helper-first for memory writes and reads, and use the event stream to wake orchestration logic. ### Decision model | Event lane | Surface | Use when | | ------------------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------- | | Run-scoped control events | `GET /v2/control/events/subscribe` | One workflow should react to ingest, reflection, handoff, feedback, checkpoint, or outcome events | | Memory read/write loop | `remember`, `recall`, `getContext`, `checkpoint` | Most application behavior | ### Minimal implementation example ```python title="event_driven_agents.py" from mubit import Client import os run_id = "claims:planner:claim-42" client = Client( endpoint=os.getenv("MUBIT_ENDPOINT", "https://api.mubit.ai"), api_key=os.environ["MUBIT_API_KEY"], run_id=run_id, transport="http", ) stream = client.subscribe({"run_id": run_id}) client.remember( session_id=run_id, agent_id="planner", content="A new adjuster note arrived for claim-42.", intent="trace", ) client.checkpoint( session_id=run_id, agent_id="planner", label="before-adjuster-review", context_snapshot="Planner stored the adjuster note and is waiting for review.", ) for event in stream: if event: print(event) break ``` ```ts title="event_driven_agents.ts" import { Client } from "@mubit-ai/sdk"; const runId = "claims:planner:claim-42"; const client = new Client({ endpoint: process.env.MUBIT_ENDPOINT ?? "https://api.mubit.ai", api_key: process.env.MUBIT_API_KEY, run_id: runId, transport: "http", }); const stream = await client.subscribe({ run_id: runId }); await client.remember({ session_id: runId, agent_id: "planner", content: "A new adjuster note arrived for claim-42.", intent: "trace", }); await client.checkpoint({ session_id: runId, agent_id: "planner", label: "before-adjuster-review", context_snapshot: "Planner stored the adjuster note and is waiting for review.", }); const reader = stream.body?.getReader(); const first = reader ? await reader.read() : null; if (first?.value) { console.log(new TextDecoder().decode(first.value)); } ``` ### Failure modes and troubleshooting | Symptom | Root cause | Fix | | --------------------------------- | ------------------------------------------------------ | -------------------------------------------- | | Workers still poll constantly | Event stream is not wired to the orchestration loop | Move wakeup logic to the control stream | | Event stream is noisy | Too many memory writes without filtering in the worker | Filter by event type in the consumer | | Recovery is hard after compaction | Events fire but context is not rebuilt | Re-read with `getContext()` after the wakeup | ### Next steps * Add planner state at [Stateful task trees](/recipes/stateful-task-trees). * Add branch-safe experimentation at [Branching memory](/recipes/branching-memory). import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## Lane-Scoped Multi-Agent Memory Isolate agent memory into named lanes so specialists see only their relevant context while sharing a common run. When multiple agents share a run, not every agent should see every piece of memory. Lanes partition the memory space so each agent reads only what it needs while still operating within the same session. ### Prerequisites * MuBit client initialized with a valid API key * A multi-agent system where agents have distinct responsibilities ### Flow 1. Register each agent with `shared_memory_lanes` declaring which lanes it participates in. 2. Ingest data with `lane=` to tag items to a specific lane. 3. Query with `lane_filter=` to retrieve only lane-scoped entries. 4. Context assembly respects the same `lane_filter` for token-budgeted prompts. ### Minimal implementation example ```python title="lane_scoped_memory.py" from mubit import Client import os run_id = "multi-agent:task-42" client = Client( endpoint=os.getenv("MUBIT_ENDPOINT", "https://api.mubit.ai"), api_key=os.environ["MUBIT_API_KEY"], run_id=run_id, transport="http", ) # Register agents with lane participation client.register_agent( agent_id="planner", role="planner", shared_memory_lanes=["planning", "shared"], write_scopes=["fact", "trace", "handoff"], ) client.register_agent( agent_id="executor", role="executor", shared_memory_lanes=["execution", "shared"], write_scopes=["fact", "trace", "observation"], ) # Planner writes to the planning lane client.remember( content="Task A depends on Task B completion before starting", intent="fact", lane="planning", agent_id="planner", ) # Executor writes to the execution lane client.remember( content="Task B completed in 2.3s with exit code 0", intent="observation", lane="execution", agent_id="executor", ) # Shared data visible to both client.remember( content="Project deadline is March 15", intent="fact", lane="shared", ) # Planner queries only planning context planning_context = client.recall( query="What are the task dependencies?", lane="planning", ) # Returns: Task A depends on Task B — but NOT the execution trace # Executor queries only execution context execution_context = client.recall( query="What tasks have completed?", lane="execution", ) # Returns: Task B completed — but NOT the planning dependencies # Either agent can query shared context shared_context = client.recall( query="What is the deadline?", lane="shared", ) # Query without lane_filter returns everything all_context = client.recall(query="task status", limit=10) ``` ```js title="lane_scoped_memory.js" import { Client } from "@mubit-ai/sdk"; const runId = "multi-agent:task-42"; const client = new Client({ endpoint: process.env.MUBIT_ENDPOINT ?? "https://api.mubit.ai", apiKey: process.env.MUBIT_API_KEY, runId, transport: "http", }); await client.registerAgent({ agentId: "planner", role: "planner", sharedMemoryLanes: ["planning", "shared"], }); await client.remember({ content: "Task A depends on Task B", intent: "fact", lane: "planning", agentId: "planner", }); // Query only the planning lane const result = await client.recall({ query: "task dependencies", lane: "planning", }); ``` ### How lanes interact with scopes Lanes and scopes are independent filtering mechanisms that compose: | Mechanism | What it filters | Set at | | ------------------------------------------ | -------------------------------------------- | ------------------------ | | **Lane** (`lane` / `lane_filter`) | Which memory partition an item belongs to | Ingest time / query time | | **Scope** (`read_scopes` / `write_scopes`) | Which entry types an agent can read or write | Agent registration time | | **User isolation** | Which authenticated user owns the data | Authentication time | An agent with `read_scopes=["fact", "lesson"]` and `lane_filter="planning"` sees only facts and lessons tagged with the `planning` lane. `lane` (MAS memory isolation) is distinct from the core data-plane retrieval lane concept (`direct_lane`). They serve different purposes and do not interact. ### Failure modes and troubleshooting | Symptom | Root cause | Fix | | ------------------------------------------------- | ------------------------------------------- | --------------------------------------------------------------------- | | Lane-filtered query returns nothing | Lane name mismatch between ingest and query | Verify the exact `lane` string matches at both ends | | Agent sees data from other lanes | Querying without `lane_filter` | An empty `lane_filter` returns all entries including unscoped ones | | Items without lanes are invisible to lane queries | Items ingested without `lane` are unscoped | Unscoped items are visible to all queries regardless of `lane_filter` | | Context assembly ignores lanes | `lane_filter` not passed to `get_context()` | Pass `lane_filter` explicitly in context requests | ### Next steps * Review the full HTTP contract at [Control HTTP reference](/api-reference/control-http#lane-parameters). * See [Multi-Agent Shared State](/recipes/multi-agent-shared-state) for scope-based coordination without lanes. * See [Step-Level Outcomes](/recipes/step-level-outcomes) for per-step reward recording. import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## Multi-Agent Shared State Coordinate specialist agents with agent registration, scoped access, handoffs, feedback, and shared context assembly. Current MuBit coordination is centered on **registered agents plus persisted coordination artifacts**. Use this pattern when a planner, reviewer, specialist, or resolver must share memory while keeping role boundaries explicit. ### Flow 1. Register each agent with the scopes it should read and write. 2. Persist shared facts, traces, rules, and lessons in one session. 3. Use `handoff()` to transfer a subtask. 4. Use `feedback()` to record the response to that handoff. 5. Use `getContext()` / `get_context()` to retrieve shared coordination context for the next agent. ### Minimal implementation example ```python title="multi_agent_shared_state.py" from mubit import Client import os run_id = "claims:planner:claim-42" client = Client( endpoint=os.getenv("MUBIT_ENDPOINT", "https://api.mubit.ai"), api_key=os.environ["MUBIT_API_KEY"], run_id=run_id, transport="http", ) client.register_agent(session_id=run_id, agent_id="planner", role="planner") client.register_agent( session_id=run_id, agent_id="coverage", role="coverage", read_scopes=["fact", "rule", "lesson", "handoff", "feedback"], write_scopes=["fact", "trace", "handoff", "feedback"], ) client.remember( session_id=run_id, agent_id="planner", content="Policy P-778 includes rental reimbursement after collision claims.", intent="fact", metadata={"claim_id": "claim-42"}, ) handoff = client.handoff( session_id=run_id, task_id="claim-42-coverage-review", from_agent_id="planner", to_agent_id="coverage", requested_action="review", content="Confirm whether rental reimbursement applies to this collision claim.", metadata={"claim_id": "claim-42"}, ) feedback = client.feedback( session_id=run_id, handoff_id=handoff["handoff_id"], from_agent_id="coverage", verdict="approve", comments="Coverage confirmed. Rental reimbursement applies for up to 14 days.", metadata={"claim_id": "claim-42"}, ) context = client.get_context( session_id=run_id, query="What did coverage conclude for claim-42?", mode="sections", sections=["handoffs", "feedback", "facts", "active_rules"], ) print({ "handoff_id": handoff.get("handoff_id"), "feedback_id": feedback.get("feedback_id"), "sections": len(context.get("section_summaries", [])), }) ``` ```ts title="multi_agent_shared_state.ts" import { Client } from "@mubit-ai/sdk"; const runId = "claims:planner:claim-42"; const client = new Client({ endpoint: process.env.MUBIT_ENDPOINT ?? "https://api.mubit.ai", api_key: process.env.MUBIT_API_KEY, run_id: runId, transport: "http", }); await client.registerAgent({ session_id: runId, agent_id: "planner", role: "planner" }); await client.registerAgent({ session_id: runId, agent_id: "coverage", role: "coverage", read_scopes: ["fact", "rule", "lesson", "handoff", "feedback"], write_scopes: ["fact", "trace", "handoff", "feedback"], }); await client.remember({ session_id: runId, agent_id: "planner", content: "Policy P-778 includes rental reimbursement after collision claims.", intent: "fact", metadata: { claim_id: "claim-42" }, }); const handoff = await client.handoff({ session_id: runId, task_id: "claim-42-coverage-review", from_agent_id: "planner", to_agent_id: "coverage", requested_action: "review", content: "Confirm whether rental reimbursement applies to this collision claim.", metadata: { claim_id: "claim-42" }, }); const feedback = await client.feedback({ session_id: runId, handoff_id: handoff.handoff_id, from_agent_id: "coverage", verdict: "approve", comments: "Coverage confirmed. Rental reimbursement applies for up to 14 days.", metadata: { claim_id: "claim-42" }, }); const context = await client.getContext({ session_id: runId, query: "What did coverage conclude for claim-42?", mode: "sections", sections: ["handoffs", "feedback", "facts", "active_rules"], }); console.log({ handoff_id: handoff.handoff_id, feedback_id: feedback.feedback_id, sections: context.section_summaries?.length ?? 0, }); ``` ### Failure modes and troubleshooting | Symptom | Root cause | Fix | | -------------------------------------- | --------------------------------------------- | ---------------------------------------------- | | Specialist sees too much memory | Agent scopes are too broad | Narrow `read_scopes` and `write_scopes` | | Coordination disappears after the turn | Handoff or feedback stayed in app memory only | Persist both with `handoff()` and `feedback()` | | Next agent repeats work | Shared context was not reassembled | Call `getContext()` before each transition | ### Next steps * Add checkpoints before compaction at [Support agent memory loop](/recipes/support-agent-loop). * See the route-level contract at [Control HTTP reference](/api-reference/control-http). import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## Prompt Optimization Lifecycle Capture outcomes → optimize → diff → activate. A human-in-the-loop workflow for evolving agent prompts from real execution data. Prompts drift. An agent that was well-tuned two weeks ago now misses cases, misroutes escalations, or over-hedges. The MuBit control plane ships an **optimization loop** that uses recorded outcomes to propose a better prompt, a diff view to review it, and a one-click approval to activate it — without touching deployed SDK code. This recipe shows the end-to-end flow. Every SDK step below has a **Console equivalent** inline — use the console when you want human-in-the-loop review and the SDK when you want to automate, schedule, or pass an explicit `llm_override`. Both paths call the same control-plane endpoints and produce identical `PromptVersion` rows. ### The loop at a glance ``` Run agents → Record outcomes → Optimize → Review diff → Activate ↑ │ └────── (next cycle) ────┘ ``` Every step is a single control-plane call. You can wire this into CI, a cron, or trigger it manually from the console's Agent Card. ### 1. Record outcomes while agents run Every interaction that ends with a judgeable result should call `record_outcome` (run-level) or `record_step_outcome` (per-step, for dense feedback). This is the signal the optimizer reads. ```python Python client.record_outcome( run_id=run_id, reference_id=evidence_id, # the specific fact / lesson / archive block the outcome is about outcome="success", # "success" | "failure" | "partial" | "neutral" signal=0.8, # -1.0..1.0 rationale="Customer confirmed the refund was processed correctly", agent_id="triage", ) ``` For multi-step agents, also record per-step signal: ```python Python client.record_step_outcome( run_id=run_id, step_id="2026-04-17T09-12-route", step_name="routing", outcome="partial", signal=0.3, rationale="Routed to billing but should have gone to compliance", directive_hint="Check billing AND compliance scopes before routing", agent_id="triage", ) ``` The optimizer weighs failures (`signal < 0`) and the `rationale` / `directive_hint` fields heavily. Invest in writing short, specific rationales — they become the material the synthesised candidate is built from. **Console equivalent:** outcomes are recorded from your agent code, not the console — the console reads them back under **Agents → your agent → Runs** (`/app/projects//agents//runs`). Even when you drive optimization entirely from the UI, the `record_outcome` / `record_step_outcome` call in your agent loop is still the signal source. ### 2. Trigger an optimization When you have enough outcomes to form an opinion (empirically: \~10–20 outcomes with at least a few negatives), ask the control plane to propose a candidate. ```python Python resp = client.optimize_prompt( agent_id="triage", project_id=project_id, # Optional: steer which model does the synthesis llm_override={ "provider": "anthropic", "model": "claude-sonnet-4-6", "temperature": 0.2, }, ) candidate = resp["candidate"] print(resp["optimization_summary"]) # human-readable rationale print(resp["confidence"]) # 0..1 print(resp["activated"]) # False by default — human review first ``` The response includes: * `candidate` — a new `PromptVersion` row with `status="candidate"` and `source="optimization"`. * `optimization_summary` — what the optimizer changed and why. * `confidence` — the optimizer's self-reported confidence. * `activated` — whether the candidate was auto-activated (default: `false`). **Console equivalent:** open the agent's **Prompts** tab (`/app/projects//agents//prompts`) and click `Suggest Optimization` on the **Active System Prompt** card. A new row appears in the Version History table with `status: candidate` and `source: optimization`, auto-expanded to show the candidate prompt, and a pending-candidate banner appears at the top of the page. The console uses the instance's default optimizer model — for `llm_override` you have to use the SDK. ### 3. Review the diff Never promote a candidate blind. Fetch the diff against the currently active version: ```python Python active = client.get_prompt(agent_id="triage") diff = client.get_prompt_diff( agent_id="triage", version_a_id=active["prompt"]["version_id"], version_b_id=candidate["version_id"], ) print(diff["diff_text"]) # unified diff format ``` **Console equivalent:** click `Review` on the pending-candidate banner, or `Compare` in the Version History row. That opens `/app/projects//agents//compare/` with the same `diff_text` rendered in a split view, the `optimization_summary` in a muted caption above the diff, and an `Approve & Activate` button at the top. What to check: 1. **Does the summary match the diff?** If the summary says "tightened escalation criteria" but the diff rewrites the tone, the optimizer hallucinated. 2. **Are edits localized?** Small, targeted edits ship safely. A full rewrite needs a canary. 3. **Does the outcome count justify the change?** The optimizer can synthesize a confident-looking candidate from 3 outcomes. Wait for more data. ### 4. Shadow test (optional but recommended) Before activating, run the candidate side-by-side with the active prompt on a known replay set. Use branching for reversibility: ```python Python # Snapshot current run so we can compare before / after checkpoint = client.checkpoint(run_id=run_id, label="pre-candidate-evaluation") # Run replay traffic. Capture outcomes for both branches. # (Your replay harness, not shown.) ``` Or, for a controlled canary, activate the candidate for a fraction of traffic by routing some runs to a duplicated agent with `agent_id="triage-canary"` whose prompt is the candidate. ### 5. Activate the winner Once you're satisfied, promote the candidate: ```python Python client.activate_prompt_version( agent_id="triage", version_id=candidate["version_id"], ) ``` Activation is atomic — in-flight runs continue with the old prompt; new runs see the new one. The previously active version transitions to `retired` and remains available for rollback. **Console equivalent:** click `Approve & Activate` on the compare page, or `Approve` on the pending-candidate banner in the Prompts tab. The console flips the status badges, retires the prior active version, and returns you to the Prompts tab — no further confirmation step. ### 6. Rollback if something breaks If the new prompt regresses, every prior version is still addressable. List versions, pick one, and reactivate: ```python Python versions = client.list_prompt_versions(agent_id="triage") prior_active = next( v for v in versions["versions"] if v["status"] == "retired" and v["source"] != "rollback" ) client.activate_prompt_version( agent_id="triage", version_id=prior_active["version_id"], ) ``` The newly activated version takes `source="rollback"` so your audit log reflects intent. **Console equivalent:** every retired version stays in Version History on the Prompts tab. Click `Compare` on a retired row to confirm the diff, then `Approve & Activate`. The activation is recorded with `source: rollback` just like the SDK path. ### Skill optimization Exactly the same loop works for skills — swap the method names: * `optimize_skill(project_id, skill_id, llm_override?)` * `list_skill_versions(skill_id)` * `get_skill_diff(skill_id, version_a_id, version_b_id)` * `activate_skill_version(skill_id, version_id)` Skills include both `parameters_schema` and `instructions` in the diff, so review both sections of the unified diff. **Console equivalent:** open a project's **Skills** tab → pick a skill (`/app/projects//skills/`). The **Active Definition** card has separate editable fields for Description, Parameters Schema, and Instructions. `Suggest Optimization` creates a candidate; the compare page at `.../compare/` renders a unified diff across all three fields; `Approve & Activate` promotes it. ### Automating the loop A common pattern: run the optimize step nightly, but **never auto-activate**. Post the diff into a Slack channel or create a ticket for a human to approve. ```python Python # Cron: nightly per agent import mubit client = mubit.Client() for agent_id in ("triage", "billing", "escalation"): resp = client.optimize_prompt(agent_id=agent_id, project_id=PROJECT) if resp["confidence"] < 0.6: continue # too speculative; skip candidate = resp["candidate"] active = client.get_prompt(agent_id=agent_id) diff = client.get_prompt_diff( agent_id=agent_id, version_a_id=active["prompt"]["version_id"], version_b_id=candidate["version_id"], ) notify_slack(agent_id, resp["optimization_summary"], diff["diff_text"]) ``` ### Related pages * [Projects, Agents, Skills, Prompts](/sdk/projects-and-agents) — the resource model behind the lifecycle. * [Step-Level Outcomes](/recipes/step-level-outcomes) — dense reward signal that feeds better optimizations. * [Activity & Audit Trail](/sdk/activity-audit) — inspect what outcomes were available when the optimizer ran. import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## Stateful Task Trees Build planner task trees with goals, actions, cycles, and checkpoints so orchestration survives retries and compaction. When planner state stays in app memory, debugging and replay are fragile. MuBit lets you persist goal structure and decision history next to the memory the planner is using. ### Mental model 1. Register planner identity. 2. Create a goal tree. 3. Record actions and reasoning cycles. 4. Save checkpoints before major transitions. 5. Retrieve the goal tree and action history when you need to replay or diagnose the run. ### Minimal implementation example ```python title="stateful_task_tree.py" from mubit import Client import os run_id = "claims:planner:claim-42" client = Client( endpoint=os.getenv("MUBIT_ENDPOINT", "https://api.mubit.ai"), api_key=os.environ["MUBIT_API_KEY"], run_id=run_id, transport="http", ) client.register_agent(session_id=run_id, agent_id="planner", role="planner") parent_goal = client.add_goal({ "run_id": run_id, "description": "Resolve claim-42 with a policy-safe decision", "priority": "critical", }) client.add_goal({ "run_id": run_id, "description": "Verify rental reimbursement coverage", "priority": "high", "parent_goal_id": parent_goal.get("id", ""), }) client.submit_action({ "run_id": run_id, "agent_id": "planner", "action_type": "retrieve", "action_json": '{"query":"collect coverage and fraud signals"}', }) client.run_cycle({ "run_id": run_id, "agent_id": "planner", "candidates": [{ "action_type": "reason", "action_json": '{"prompt":"pick the safest next verification step"}', "score": 0.88, "rationale": "coverage confirmation should happen before settlement drafting", }], }) client.checkpoint( session_id=run_id, agent_id="planner", label="after-goal-tree-build", context_snapshot="Planner built the first goal tree and recorded an initial decision cycle.", ) goal_tree = client.get_goal_tree({"run_id": run_id}) actions = client.get_action_log({"run_id": run_id, "limit": 20}) print({ "goal_nodes": len(goal_tree.get("goals", [])), "actions": len(actions.get("entries", [])), }) ``` ```ts title="stateful_task_tree.ts" import { Client } from "@mubit-ai/sdk"; const runId = "claims:planner:claim-42"; const client = new Client({ endpoint: process.env.MUBIT_ENDPOINT ?? "https://api.mubit.ai", api_key: process.env.MUBIT_API_KEY, run_id: runId, transport: "http", }); await client.registerAgent({ session_id: runId, agent_id: "planner", role: "planner" }); const parentGoal = await client.addGoal({ run_id: runId, description: "Resolve claim-42 with a policy-safe decision", priority: "critical", }); await client.addGoal({ run_id: runId, description: "Verify rental reimbursement coverage", priority: "high", parent_goal_id: parentGoal.id, }); await client.submitAction({ run_id: runId, agent_id: "planner", action_type: "retrieve", action_json: '{"query":"collect coverage and fraud signals"}', }); await client.runCycle({ run_id: runId, agent_id: "planner", candidates: [{ action_type: "reason", action_json: '{"prompt":"pick the safest next verification step"}', score: 0.88, rationale: "coverage confirmation should happen before settlement drafting", }], }); await client.checkpoint({ session_id: runId, agent_id: "planner", label: "after-goal-tree-build", context_snapshot: "Planner built the first goal tree and recorded an initial decision cycle.", }); const goalTree = await client.getGoalTree({ run_id: runId }); const actions = await client.getActionLog({ run_id: runId, limit: 20 }); console.log({ goal_nodes: goalTree.goals?.length ?? 0, actions: actions.entries?.length ?? 0, }); ``` ### Failure modes and troubleshooting | Symptom | Root cause | Fix | | ------------------------------------- | ----------------------------------- | ------------------------------------------------------------ | | Planner decisions are hard to replay | Actions and cycles were not stored | Record both action logs and cycle history | | Goal hierarchy becomes flat and noisy | Parent-child goals were not used | Build explicit goal trees and read them through `goals/tree` | | Compaction loses planner intent | No checkpoint before the transition | Save checkpoints at major decision boundaries | ### Next steps * Add event handling at [Building event-driven agents](/recipes/event-driven-agents). * Review the explicit state surface at [State management endpoints](/api-reference/state-management). import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## Step-Level Outcomes and Process Rewards Record per-step reward signals during agentic runs so reflection produces richer, step-attributed lessons. Run-level outcomes tell you whether the whole run succeeded. Step-level outcomes tell you which specific step caused success or failure. MuBit stores these signals and feeds them into reflection so lessons are attributed to the exact step that mattered. ### Prerequisites * MuBit client initialized with a valid API key * A multi-step agent run where each step produces an observable result ### Flow 1. Execute an agent step (tool call, LLM inference, decision). 2. Record a step outcome with signal, rationale, and optional directive hint. 3. Repeat for each step in the run. 4. Reflect with `include_step_outcomes=True` to produce step-attributed lessons. 5. Use `record_outcome()` at the end for the overall run-level signal. ### Minimal implementation example ```python title="step_outcomes.py" from mubit import Client import os run_id = "agent:planner:task-123" client = Client( endpoint=os.getenv("MUBIT_ENDPOINT", "https://api.mubit.ai"), api_key=os.environ["MUBIT_API_KEY"], run_id=run_id, transport="http", ) # Step 1: Planning plan = call_llm("Break down the task into sub-steps") client.record_step_outcome( step_id="step-1-planning", step_name="initial_planning", outcome="success", signal=0.8, rationale="Generated a clear 3-step plan with dependencies identified", directive_hint="Include sub-task dependencies explicitly in plans", ) # Step 2: Tool call tool_result = execute_tool("search_api", query="relevant docs") client.record_step_outcome( step_id="step-2-search", step_name="search_api", outcome="failure", signal=-0.6, rationale="Search returned no results — query was too narrow", directive_hint="Use broader search terms before narrowing", ) # Step 3: Recovery recovery = call_llm("Retry with broader query") client.record_step_outcome( step_id="step-3-recovery", step_name="search_retry", outcome="success", signal=0.9, rationale="Broader query found the target document", ) # Reflect with step outcomes to produce step-attributed lessons lessons = client.reflect(include_step_outcomes=True) # Run-level outcome client.record_outcome( outcome="success", signal=0.7, rationale="Task completed after one retry", ) ``` ```js title="step_outcomes.js" import { Client } from "@mubit-ai/sdk"; const runId = "agent:planner:task-123"; const client = new Client({ endpoint: process.env.MUBIT_ENDPOINT ?? "https://api.mubit.ai", apiKey: process.env.MUBIT_API_KEY, runId, transport: "http", }); // Step 1: Planning await client.recordStepOutcome({ stepId: "step-1-planning", stepName: "initial_planning", outcome: "success", signal: 0.8, rationale: "Generated a clear 3-step plan", directiveHint: "Include sub-task dependencies explicitly", }); // Step 2: Failed tool call await client.recordStepOutcome({ stepId: "step-2-search", stepName: "search_api", outcome: "failure", signal: -0.6, rationale: "Search returned no results — query too narrow", directiveHint: "Use broader search terms before narrowing", }); // Reflect with step outcomes const lessons = await client.reflect({ includeStepOutcomes: true }); ``` ### Field reference | Field | Type | Required | Description | | ---------------- | ------ | -------- | --------------------------------------------- | | `step_id` | string | yes | Unique step identifier within the run | | `step_name` | string | no | Human-readable label for the step | | `outcome` | string | yes | `success`, `failure`, `partial`, or `neutral` | | `signal` | float | no | Reward signal from -1.0 (worst) to 1.0 (best) | | `rationale` | string | no | Explanation of why the outcome was assigned | | `directive_hint` | string | no | Hindsight guidance for future runs | | `agent_id` | string | no | Agent that performed the step | | `metadata_json` | string | no | Arbitrary structured metadata | ### Combining with lane-scoped memory Step outcomes work naturally with lanes. If your multi-agent system uses lane-scoped memory, record step outcomes from each agent and then reflect with both lane and step context: ```python # Agent "planner" records its step outcomes in the run client.record_step_outcome( step_id="plan-v1", step_name="planning", outcome="success", signal=0.9, agent_id="planner", ) # Reflect across all step outcomes client.reflect(include_step_outcomes=True) ``` ### Failure modes and troubleshooting | Symptom | Root cause | Fix | | --------------------------------------------------------- | --------------------------------------- | --------------------------------------------------- | | Reflection produces generic lessons despite step outcomes | `include_step_outcomes` not set | Pass `include_step_outcomes=True` in `reflect()` | | Step outcome not accepted | Missing `step_id` or `outcome` | Both fields are required | | Lessons lack step attribution | Step outcomes recorded after reflection | Record step outcomes before calling `reflect()` | | Too many step outcomes dilute signal | Recording outcomes for trivial steps | Only record outcomes for decision-significant steps | ### Next steps * Review the full HTTP contract at [Control HTTP reference](/api-reference/control-http#step-level-outcome-request-shape). * Review the gRPC surface at [Control gRPC reference](/api-reference/control-grpc#step-level-outcomes). * See [Lane-Scoped Multi-Agent Memory](/recipes/lane-scoped-memory) for memory isolation patterns. import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## Support Agent Memory Loop Build a support-agent loop with context assembly, checkpointing, reflection, and outcome reinforcement. This is the default long-running support-agent pattern in current MuBit: 1. store customer facts, traces, and preferences 2. assemble context before each response 3. checkpoint before compaction or risky transitions 4. reflect after the attempt 5. record the outcome so the next similar case gets better guidance 6. archive exact artifacts when later steps need exact reuse instead of only semantic recall ### Minimal implementation example ```python title="support_agent_memory_loop.py" from mubit import Client import os run_id = "support:acme:ticket-42" client = Client( endpoint=os.getenv("MUBIT_ENDPOINT", "https://api.mubit.ai"), api_key=os.environ["MUBIT_API_KEY"], run_id=run_id, transport="http", ) client.register_agent( session_id=run_id, agent_id="support-agent", role="support", # `archive_block` must be listed explicitly — archive() is its own scope, # distinct from "lesson"/"fact"/"trace". Agents without it get # PermissionDenied on archive() calls. read_scopes=["fact", "lesson", "rule", "handoff", "feedback", "archive_block"], write_scopes=["fact", "trace", "lesson", "handoff", "feedback", "archive_block"], ) client.remember( session_id=run_id, agent_id="support-agent", content="Customer Taylor prefers concise Friday updates and wants billing fixes summarized in one paragraph.", intent="fact", metadata={"customer": "taylor", "source": "ticket"}, ) context = client.get_context( session_id=run_id, query="Draft the next customer-safe update for Taylor.", mode="full", max_token_budget=700, ) checkpoint = client.checkpoint( session_id=run_id, agent_id="support-agent", label="pre-response-compaction", context_snapshot=context.get("context_block", ""), metadata={"stage": "drafting"}, ) archived = client.archive( session_id=run_id, agent_id="support-agent", content="Original billing diff and exact remediation note for ticket-42", artifact_kind="billing_postmortem", labels=["billing", "exact"], ) exact = client.dereference( session_id=run_id, reference_id=archived["reference_id"], ) reflection = client.reflect(session_id=run_id) # Read the lesson id off the reflect response directly — it's populated # server-side from the persistence call. Avoid a round-trip through # `client.lessons()` which races with the vector store's index propagation # and can return the run as empty for a few seconds after reflect. lesson_id = next( (l.get("lesson_id") for l in (reflection.get("lessons") or []) if l.get("lesson_id")), None, ) if lesson_id: client.record_outcome( session_id=run_id, agent_id="support-agent", reference_id=lesson_id, outcome="success", signal=0.8, rationale="The customer update matched the stored preference and resolved the billing question cleanly.", ) strategies = client.surface_strategies( session_id=run_id, lesson_types=["success", "failure"], max_strategies=3, ) print({ "checkpoint_id": checkpoint.get("checkpoint_id"), "lessons_stored": reflection.get("lessons_stored"), "strategies": len(strategies.get("strategies", [])), "exact_reference_found": exact.get("found"), }) ``` ### What matters operationally * `get_context` is the pre-compaction memory surface. * `checkpoint` preserves the critical context snapshot before the LLM window is compacted. * `archive` plus `dereference` is the exact-reference path for artifacts that must come back verbatim later. * `reflect` extracts reusable lessons and rules from the run. * `record_outcome` changes later ranking so good lessons appear earlier next time. ### Self-contained runnable example Copy the two files below into an empty directory, fill in `MUBIT_API_KEY`, and `python support_agent.py` will run the full loop end-to-end without any LLM provider setup. The "LLM response" is a deterministic stub so you can see every MuBit call succeed before wiring in a real model. ```bash title=".env" MUBIT_API_KEY=mbt___ MUBIT_ENDPOINT=https://api.mubit.ai ``` ```python title="support_agent.py" """Self-contained support-agent loop against MuBit. Prereqs: pip install mubit-sdk>=0.6.0 python-dotenv Run: python support_agent.py """ from __future__ import annotations import os import uuid from dotenv import load_dotenv from mubit import Client load_dotenv() RUN_ID = f"support:demo:{uuid.uuid4().hex[:8]}" AGENT = "support-agent" def stub_llm(prompt: str, context: str) -> str: """Deterministic stand-in for a real LLM so the example is self-contained.""" return ( "Hi Taylor — here is your Friday billing summary in one paragraph: " "the duplicate charge was refunded, the recurring invoice was corrected, " "and no further action is needed. (context_chars=%d)" % len(context) ) def main() -> None: client = Client(run_id=RUN_ID) # 1. Register the agent and its scopes client.register_agent( session_id=RUN_ID, agent_id=AGENT, role="support", read_scopes=["fact", "lesson", "rule", "handoff"], write_scopes=["fact", "trace", "lesson", "handoff"], ) # 2. Store a durable customer fact client.remember( session_id=RUN_ID, agent_id=AGENT, content="Customer Taylor prefers concise Friday updates; bills should be " "summarized in one paragraph, not a bulleted list.", intent="fact", metadata={"customer": "taylor", "source": "ticket-42"}, ) # 3. Assemble context before the response ctx = client.get_context( session_id=RUN_ID, query="Draft the next customer-safe billing update for Taylor.", mode="full", max_token_budget=700, ) context_block = ctx.get("context_block", "") # 4. Checkpoint before the generation (compaction safety) checkpoint = client.checkpoint( session_id=RUN_ID, agent_id=AGENT, label="pre-response", context_snapshot=context_block, metadata={"stage": "drafting"}, ) # 5. Generate the response (stub in place of a real LLM) response = stub_llm("Draft the billing update.", context_block) # 6. Ingest the response as a trace (kept distinct from the fact) client.remember( session_id=RUN_ID, agent_id=AGENT, content=f"Agent response: {response}", intent="trace", ) # 7. Reflect after the attempt. The lesson id is returned inline on the # response so we don't need a separate `lessons()` call (which can race # with the vector-store index right after a write). reflection = client.reflect(session_id=RUN_ID) lesson_id = next( (l.get("lesson_id") for l in (reflection.get("lessons") or []) if l.get("lesson_id")), None, ) # 8. Reinforce the lesson that drove the right format if lesson_id: client.record_outcome( session_id=RUN_ID, agent_id=AGENT, reference_id=lesson_id, outcome="success", signal=0.8, rationale="Response matched the stored preference for concise paragraph format.", ) # 9. Surface reusable strategies for the next similar ticket strategies = client.surface_strategies( session_id=RUN_ID, lesson_types=["success", "failure"], max_strategies=3, ) print({ "run_id": RUN_ID, "checkpoint_id": checkpoint.get("checkpoint_id"), "lessons_stored": reflection.get("lessons_stored", 0), "strategies_surfaced": len(strategies.get("strategies", [])), "response_preview": response[:80], }) if __name__ == "__main__": main() ``` **Expected output** (values will differ, but the shape should match): ```json { "run_id": "support:demo:a1b2c3d4", "checkpoint_id": "ck_...", "lessons_stored": 1, "strategies_surfaced": 2, "response_preview": "Hi Taylor — here is your Friday billing summary..." } ``` Once this works, swap `stub_llm` for a real provider (`openai.ChatCompletion.create`, `anthropic.Anthropic().messages.create`, etc.) and feed `context_block` as the system message. Nothing else in the loop needs to change. ### Next steps * Coordinate multiple specialists at [Multi-agent shared state](/recipes/multi-agent-shared-state). * Debug weak memory quality with [Control HTTP reference](/api-reference/control-http). import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## Temporal Memory Patterns Use occurrence time, temporal range queries, staleness detection, and budget control for time-aware agent memory. MuBit tracks two time dimensions for every memory entry: **ingestion time** (when the system learned it) and **occurrence time** (when the event actually happened). This distinction is critical for agents that reason about historical events. ### Storing events with occurrence time When ingesting facts about past events, set `occurrence_time` to record when the event happened. Without it, the system only knows when the fact was ingested. ```python import time # Event happened 3 days ago, ingested now client.remember( content="New CI/CD pipeline reduced deployment time by 60%.", intent="fact", occurrence_time=int(time.time()) - 86400 * 3, ) # Historical event from January 2025 client.remember( content="Server migration to AWS completed with zero downtime.", intent="fact", occurrence_time=1736899200, # Jan 15 2025 UTC ) ``` ```js // Event happened 3 days ago, ingested now await client.remember({ content: "New CI/CD pipeline reduced deployment time by 60%.", intent: "fact", occurrence_time: Math.floor(Date.now() / 1000) - 86400 * 3, }); // Historical event from January 2025 await client.remember({ content: "Server migration to AWS completed with zero downtime.", intent: "fact", occurrence_time: 1736899200, // Jan 15 2025 UTC }); ``` ### Querying by time range Use `min_timestamp` and `max_timestamp` to filter evidence to a specific time window. The filter checks `occurrence_time` first, falling back to ingestion time. ```python # "What happened in January 2025?" results = client.recall( query="What technical changes were made?", min_timestamp=1735689600, # Jan 1 2025 max_timestamp=1738367999, # Jan 31 2025 ) for evidence in results["evidence"]: print(f" {evidence['content'][:80]}") ``` ```js const results = await client.recall({ query: "What technical changes were made?", min_timestamp: 1735689600, max_timestamp: 1738367999, }); results.evidence.forEach(e => console.log(e.content.slice(0, 80))); ``` Without temporal bounds, "What happened last week?" uses natural language temporal intent detection and prioritizes entries by occurrence time in the recency ranking. ### Handling stale facts When a newer fact contradicts an older one, MuBit marks the older entry as stale and deprioritizes it in ranking. The staleness metadata is available in evidence responses. ```python # Store contradicting facts client.remember( content="The office is in Building A, Floor 3.", intent="fact", metadata={"speaker": "facilities"}, ) # Later... client.remember( content="The office moved to Building B, Floor 7.", intent="fact", metadata={"speaker": "facilities"}, ) # Query — newer fact ranks higher, older is marked stale results = client.recall(query="Where is the office?") for evidence in results["evidence"]: stale = evidence.get("is_stale", False) status = " [STALE]" if stale else "" print(f" {evidence['content'][:60]}{status}") ``` ```js const results = await client.recall({ query: "Where is the office?" }); results.evidence.forEach(e => { const status = e.is_stale ? " [STALE]" : ""; console.log(` ${e.content.slice(0, 60)}${status}`); }); ``` Stale entries are still returned for transparency. The ranking penalty ensures they appear below the current fact. Filter them out in your application if you only want current information. ### Budget control for latency-sensitive agents The `budget` parameter controls the depth of retrieval. Use `"low"` for real-time agents and `"high"` for accuracy-critical offline analysis. | Budget | Behavior | Typical latency | | -------- | --------------------------------------- | --------------- | | `"low"` | Fewer candidates, skip deep traversal | \< 500ms | | `"mid"` | Standard retrieval (default) | 500ms–2s | | `"high"` | More candidates, deeper graph traversal | 1–5s | ```python # Fast retrieval for a real-time chatbot fast = client.recall(query="user question", budget="low") # Deep retrieval for a research report deep = client.recall(query="comprehensive analysis topic", budget="high") ``` ```js // Fast const fast = await client.recall({ query: "user question", budget: "low" }); // Deep const deep = await client.recall({ query: "analysis topic", budget: "high" }); ``` ### Next steps * See [SDK methods](/sdk/sdk-methods#temporal-and-quality-features) for the full parameter reference. * Use [mental models](/sdk/sdk-methods#mental-models) to store consolidated entity summaries. * Check [staleness detection](/sdk/sdk-methods#staleness-detection) for handling knowledge updates. import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## Control gRPC API Current public MuBit ControlService RPC surface for memory, diagnostics, learning loop, coordination, and explicit planning state. This page summarizes the current public `mubit.v1.ControlService` RPC surface. Use it for generated-client debugging and transport-parity checks. ### Service identity * Package: `mubit.v1` * Service: `ControlService` ### Core memory RPCs | Group | RPCs | | -------------------------- | ------------------------------------------------------------------------------------------- | | Ingestion | `Ingest`, `BatchInsert`, `GetIngestJob`, `GetRunIngestStats`, `ArchiveBlock`, `Dereference` | | Retrieval and context | `Query`, `GetContext`, `ListActivity`, `ExportActivity`, `GetRunSnapshot` | | Diagnostics and reflection | `Diagnose`, `Reflect`, `ListLessons`, `DeleteLesson`, `GetMemoryHealth` | ### MAS and learning-loop RPCs | Group | RPCs | | ---------------------------- | ----------------------------------------------------------------------- | | Agent identity | `RegisterAgent`, `ListAgents`, `AgentHeartbeat`, `AppendActivity` | | Compaction and reinforcement | `Checkpoint`, `RecordOutcome`, `RecordStepOutcome`, `SurfaceStrategies` | | Coordination | `CreateHandoff`, `SubmitFeedback` | | Run management | `DeleteRun`, `LinkRun`, `UnlinkRun` | ### Managed resources: Projects, Agents, Skills, Prompts Managed MuBit deployments version agent configuration server-side so prompt + skill changes can be reviewed, rolled out, and rolled back without touching deployed SDK code. | Group | RPCs | | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | Projects | `CreateProject`, `GetProject`, `ListProjects`, `UpdateProject`, `DeleteProject` | | Agent Definitions | `CreateAgentDefinition`, `GetAgentDefinition`, `ListAgentDefinitions`, `UpdateAgentDefinition`, `DeleteAgentDefinition` | | Prompt Lifecycle | `SetPrompt`, `GetPrompt`, `ListPromptVersions`, `ActivatePromptVersion`, `OptimizePrompt`, `GetPromptDiff` | | Skill Lifecycle | `CreateSkill`, `GetSkill`, `ListSkills`, `UpdateSkill`, `DeleteSkill`, `ListSkillVersions`, `ActivateSkillVersion`, `OptimizeSkill`, `GetSkillDiff` | | Run history | `ListRunHistory`, `GetRunHistory` | | Control sessions | `CreateControlSession`, `GetControlSession`, `CloseControlSession` | #### PromptVersion / SkillVersion lifecycle Both prompt and skill versions share the same state machine: * `status` transitions: `candidate` → `active` (via `ActivatePromptVersion` / `ActivateSkillVersion`) → `retired` (when a newer version becomes active) → `archived`. * `source` values: `manual` (written via `SetPrompt` / `CreateSkill`), `optimization` (minted by `OptimizePrompt` / `OptimizeSkill`), `rollback` (restored from a retired version). * Each version carries outcome aggregates: `avg_outcome_score`, `outcome_count`, and `optimization_summary`. `OptimizePrompt` and `OptimizeSkill` accept an optional `LlmOverride` (`provider`, `model`, `temperature`, `max_tokens`, `timeout_ms`) so you can steer which model synthesises the candidate. `GetPromptDiff` / `GetSkillDiff` return a unified `diff_text` comparing any two versions — the same text the console surfaces in its review UI. ### Deprecated planning state RPCs The following RPCs remain wired for backward compatibility but are no longer the recommended path. Use external task/orchestration systems (Linear, Jira, LangGraph, CrewAI) for goals, actions, and decision cycles. See [State management endpoints](/api-reference/state-management) for guidance. | Group | RPCs | Recommendation | | --------------- | --------------------------------------------------------------- | ------------------------------------------------------- | | Goals | `AddGoal`, `UpdateGoal`, `ListGoals`, `GetGoalTree` | **Deprecated** — track goals in your task system | | Actions | `SubmitAction`, `GetActionLog` | **Deprecated** — use LangGraph / CrewAI | | Decision cycles | `RunCycle`, `GetCycleHistory` | **Deprecated** — use your agent orchestration framework | | Variables | `SetVariable`, `GetVariable`, `ListVariables`, `DeleteVariable` | Still supported for run-scoped working memory | | Concepts | `DefineConcept`, `ListConcepts` | Still supported for typed concept definitions | ### Practical parity checks * Helper-first SDKs should map cleanly onto these RPC groups even when they expose more ergonomic names. * `GetContext` is the primary gRPC compaction-safe context assembly surface. * `ListActivity` and `ExportActivity` are the chronological audit/browse surfaces; use them when you need exact ordering and pagination rather than ranked retrieval. * `Diagnose`, `GetMemoryHealth`, `Reflect`, `RecordOutcome`, `RecordStepOutcome`, and `SurfaceStrategies` are the key loop-inspection and reinforcement RPCs. * `RecordStepOutcome` records per-step process rewards with signal, rationale, and directive hint for dense RL within a run. * `Reflect` accepts `step_id`, `last_n_items`, and `include_step_outcomes` for step-wise scoped reflection. * `Ingest`, `Query`, and `GetContext` accept lane fields (`lane`, `lane_filter`) for multi-agent memory isolation within a shared run. * `ArchiveBlock` and `Dereference` are the exact-reference pair: archive an exact artifact once, then recover it later by stable `reference_id`. ### Evidence provenance and telemetry `Query` and `GetContext` now expose exact-reference provenance on surfaced evidence: * `retrieval_mode` * `reference_id` * `referenceable` * `origin_entry_type` `GetContext` also returns context-pressure telemetry: * source counts by entry type and retrieval mode * evidence candidates considered * evidence dropped by token budget * exact references surfaced ### Step-level outcomes `RecordStepOutcome` records a per-step process reward signal within a run, complementing the run-level `RecordOutcome`. Use it after each agentic step where you want dense feedback for reflection. | Field | Type | Description | | ---------------- | ------ | --------------------------------------------------------- | | `run_id` | string | Session/run identifier | | `step_id` | string | Unique step identifier within the run | | `step_name` | string | Human-readable step label (e.g. `"initial_planning"`) | | `outcome` | string | One of `success`, `failure`, `partial`, `neutral` | | `signal` | float | Reward signal from -1.0 to 1.0 | | `rationale` | string | Why this outcome was assigned | | `directive_hint` | string | Hindsight guidance for future runs (Hindsight-Guided OPD) | | `agent_id` | string | Agent that performed the step | | `user_id` | string | Optional logical user scope | | `metadata_json` | string | Arbitrary JSON metadata | Response returns `step_outcome_id` and `accepted`. Emits `context.step_outcome_recorded` event. ### Lane-scoped memory Lanes partition memory within a shared run so each agent sees only relevant entries. | RPC / Field | Description | | ------------------------------------------ | ----------------------------------------------------- | | `IngestItem.lane` | Tags ingested items with a named lane | | `AgentQueryRequest.lane_filter` | Retrieves only entries tagged with the specified lane | | `ContextRequest.lane_filter` | Filters context assembly by lane | | `AgentRegisterRequest.shared_memory_lanes` | Declares which lanes an agent participates in | Items without a lane are visible to all queries. Items with a lane are visible only when `lane_filter` matches or is empty. `lane` (MAS memory isolation) is distinct from the core data-plane retrieval lane concept used in direct search routing. ### Failure modes and troubleshooting | Symptom | Root cause | Fix | | -------------------------------------------------- | ------------------------------------------------- | ---------------------------------------------------------------------------- | | gRPC behavior differs from helper behavior | You are comparing a helper to the wrong raw RPC | Check whether the helper wraps `Query`, `GetContext`, or another control RPC | | Reflection succeeds but learning feels weak | Outcomes are not being recorded | Pair `Reflect` with `RecordOutcome` | | Context feels incomplete | Wrong mode or budget settings | Validate `ContextRequest` mode, sections, and token budget fields | | Step outcomes recorded but reflection ignores them | `include_step_outcomes` defaults to false | Pass `include_step_outcomes=True` in `ReflectRequest` | | Lane-filtered query returns nothing | Lane name mismatch or items ingested without lane | Verify the `lane` value at ingest time matches `lane_filter` at query time | ### Next steps * Review HTTP route names at [Control HTTP reference](/api-reference/control-http). * Review the helper-first SDK surfaces at [SDK methods](/sdk/sdk-methods). import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## Control HTTP API Current public MuBit control-plane HTTP routes for memory, context, diagnostics, coordination, and explicit planning state. This page documents the current public HTTP control surface. In application code, prefer the helper-first SDK methods where they exist and use these routes as wire-contract and debugging references. ### Core memory routes | Route | Method | Purpose | Recommended SDK surface | | --------------------------------- | ------ | -------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | | `/v2/control/ingest` | `POST` | Async ingest for facts, traces, lessons, rules, WM, and other typed memory | `remember()` for normal writes, raw `client.ingest(...)` when you need job control | | `/v2/control/batch_insert` | `POST` | Compatibility bulk write path | raw `client.batchInsert(...)` / `batch_insert(...)` | | `/v2/control/ingest/jobs/:job_id` | `GET` | Ingest job status and completion | raw `client.getIngestJob(...)` / `get_ingest_job(...)` | | `/v2/control/query` | `POST` | Answer-oriented routed retrieval | `recall()` or raw `client.query(...)` | | `/v2/control/context` | `POST` | Pre-assembled context block with disclosure modes and budget control | `getContext()` / `get_context()` | | `/v2/control/activity` | `POST` | Chronological memory/activity browse surface with scope and type filters | raw `client.listActivity(...)` when available, otherwise direct HTTP | | `/v2/control/activity/export` | `POST` | Export chronological memory/activity as JSONL | direct HTTP or operator export tooling | | `/v2/control/archive` | `POST` | Write an exact reusable artifact block and return a stable `reference_id` | `archive()` / `archive_block()` | | `/v2/control/dereference` | `POST` | Fetch exact stored content by `reference_id` | `dereference()` | | `/v2/control/diagnose` | `POST` | Failure-path lesson surfacing | `diagnose()` | | `/v2/control/memory_health` | `POST` | Memory quality, staleness, contradictions, and section health | `memoryHealth()` / `memory_health()` | | `/v2/control/reflect` | `POST` | Extract lessons and rules from run evidence | `reflect()` | | `/v2/control/lessons` | `POST` | List stored lessons for a run/session scope | raw `client.lessons(...)` | | `/v2/control/lessons/delete` | `POST` | Delete a stored lesson | `forget()` or raw lesson delete | ### MAS and learning-loop routes | Route | Method | Purpose | Recommended SDK surface | | ----------------------------- | ------ | ---------------------------------------------------------------- | ----------------------------------------------- | | `/v2/control/agents` | `POST` | List registered agents for the run | `listAgents()` / `list_agents()` | | `/v2/control/agents/register` | `POST` | Register an agent identity and its scopes | `registerAgent()` / `register_agent()` | | `/v2/control/checkpoint` | `POST` | Save a pre-compaction or pre-transition checkpoint | `checkpoint()` | | `/v2/control/outcome` | `POST` | Reinforce a lesson or rule after success/failure/partial outcome | `recordOutcome()` / `record_outcome()` | | `/v2/control/step_outcome` | `POST` | Record a per-step process reward signal within a run | `recordStepOutcome()` / `record_step_outcome()` | | `/v2/control/strategies` | `POST` | Surface repeated patterns from stored lessons and rules | `surfaceStrategies()` / `surface_strategies()` | | `/v2/control/handoff` | `POST` | Persist a coordination handoff between agents | `handoff()` | | `/v2/control/feedback` | `POST` | Persist a response to a handoff or review request | `feedback()` | ### Explicit planning and state routes | Route | Method | Purpose | | ------------------------------ | ------ | ----------------------------------- | | `/v2/control/goals/add` | `POST` | Add a goal | | `/v2/control/goals/update` | `POST` | Update a goal | | `/v2/control/goals/list` | `POST` | List goals | | `/v2/control/goals/tree` | `POST` | Retrieve the nested goal tree | | `/v2/control/variables/set` | `POST` | Set an explicit run-scoped variable | | `/v2/control/variables/get` | `POST` | Get a variable | | `/v2/control/variables/list` | `POST` | List variables | | `/v2/control/variables/delete` | `POST` | Delete a variable | | `/v2/control/concepts/define` | `POST` | Define a concept | | `/v2/control/concepts/list` | `POST` | List concepts | | `/v2/control/actions/submit` | `POST` | Record an action | | `/v2/control/actions/log` | `POST` | Read the action log | | `/v2/control/cycles/run` | `POST` | Record a reasoning / planning cycle | | `/v2/control/cycles/history` | `POST` | Read cycle history | ### Run management and agent lifecycle routes | Route | Method | Purpose | Recommended SDK surface | | ------------------------------- | ------ | ---------------------------------------------------------------- | ----------------------------------------------------------------- | | `/v2/control/runs` | `GET` | List recent runs | raw `client.listRuns(...)` / `list_runs(...)` | | `/v2/control/runs/link` | `POST` | Link a child run to a parent run | raw `client.linkRun(...)` / `link_run(...)` | | `/v2/control/runs/unlink` | `POST` | Unlink a previously linked run | raw `client.unlinkRun(...)` / `unlink_run(...)` | | `/v2/control/delete_run` | `POST` | Delete a run and its associated data | raw `client.deleteRun(...)` / `delete_run(...)` | | `/v2/control/agents/heartbeat` | `POST` | Agent liveness heartbeat signal | raw `client.agentHeartbeat(...)` / `agent_heartbeat(...)` | | `/v2/control/activities/append` | `POST` | Append activity traces to a run | raw `client.appendActivity(...)` / `append_activity(...)` | | `/v2/control/context/snapshot` | `POST` | Full context snapshot including working memory, attention, goals | raw `client.contextSnapshot(...)` / `get_run_snapshot(...)` | | `/v2/control/events/subscribe` | `GET` | Stream control events via SSE | raw `client.subscribe(...)` | | `/v2/control/ingest/stats` | `POST` | Get per-run ingest statistics | raw `client.getRunIngestStats(...)` / `get_run_ingest_stats(...)` | ### Operating guidance * Prefer helper methods for normal application code. * Use raw route-level control when you need exact request-shape control, ingest-job polling, or contract debugging. * Treat `/v2/control/context` as the default compaction-safe context assembly surface for long-running systems. * Treat `/v2/control/activity` as the chronological audit surface for stored MuBit data. It is separate from semantic `query`. * Use `/v2/control/archive` plus `/v2/control/dereference` when later steps need exact artifact fidelity rather than only semantic discovery. * Use `diagnose`, `memory_health`, and `strategies` before changing prompts blindly. ### Temporal query parameters `POST /v2/control/query` and `POST /v2/control/context` accept temporal filtering and budget control: | Field | Type | Required | Description | | --------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------- | | `min_timestamp` | int64 | no | Lower bound for temporal filter (unix seconds, inclusive). Filters by `occurrence_time` with `created_at` fallback. | | `max_timestamp` | int64 | no | Upper bound for temporal filter (unix seconds, inclusive). | | `budget` | string | no | Search budget tier: `"low"`, `"mid"` (default), `"high"`. Controls latency/quality tradeoff. | #### Staleness fields in query evidence Evidence items in query and context responses may include staleness metadata: | Field | Type | Description | | --------------- | ------ | --------------------------------------------------------- | | `is_stale` | bool | Whether this entry has been superseded by a newer version | | `superseded_by` | string | ID of the superseding entry (empty if not stale) | Stale entries are returned with a ranking penalty. Filter them in your application if you only want current information. #### Occurrence time on ingest `POST /v2/control/ingest` items accept an occurrence time field: | Field | Type | Required | Description | | ----------------- | ----- | -------- | ----------------------------------------------------------------------------------------------------------------------------- | | `occurrence_time` | int64 | no | When the event actually occurred (unix seconds). Distinct from ingestion time. Used for temporal queries and recency ranking. | ### Evidence provenance and context pressure `/v2/control/query` and `/v2/control/context` now expose reuse provenance on surfaced evidence: * `retrieval_mode`: `semantic`, `exact_reference`, `checkpoint`, `rule_overlay`, `lesson_overlay`, or `working_memory` * `reference_id`: stable exact-reference ID when available * `referenceable`: whether the source can be dereferenced later * `origin_entry_type`: typed LTM source such as `fact`, `lesson`, `rule`, `archive_block`, or `checkpoint` `/v2/control/context` also reports context-pressure telemetry: * requested token budget * `budget_used` * `budget_remaining` * source counts by entry type * source counts by retrieval mode * `evidence_candidates_considered` * `evidence_dropped_by_budget` * `exact_references_surfaced` ### Step-level outcome request shape `POST /v2/control/step_outcome` records a per-step process reward signal, complementing the run-level `/v2/control/outcome`. | Field | Type | Required | Description | | ---------------- | ------ | -------- | ------------------------------------------------- | | `run_id` | string | yes | Session/run identifier | | `step_id` | string | yes | Unique step identifier within the run | | `step_name` | string | no | Human-readable step label | | `outcome` | string | yes | One of `success`, `failure`, `partial`, `neutral` | | `signal` | float | no | Reward signal from -1.0 to 1.0 | | `rationale` | string | no | Why this outcome was assigned | | `directive_hint` | string | no | Hindsight guidance for future runs | | `agent_id` | string | no | Agent that performed the step | | `user_id` | string | no | Logical user scope | | `metadata_json` | string | no | Arbitrary JSON metadata | Response: `{ "step_outcome_id": "...", "accepted": true }`. Use after each agentic step where you want dense reward signal. Then call `reflect(include_step_outcomes=True)` to produce step-attributed lessons. ### Lane parameters Lanes partition memory within a shared run for multi-agent isolation. | Route | Field | Description | | ----------------------------- | --------------------- | ----------------------------------------------------- | | `/v2/control/ingest` | `lane` (on each item) | Tags ingested items with a named lane | | `/v2/control/query` | `lane_filter` | Retrieves only entries tagged with the specified lane | | `/v2/control/context` | `lane_filter` | Filters context assembly by lane | | `/v2/control/agents/register` | `shared_memory_lanes` | Declares which lanes an agent participates in | Items without a lane are visible to all queries. Items with a lane are visible only when `lane_filter` matches or is empty. `lane` (MAS memory isolation) is distinct from the core data-plane retrieval lane concept used in direct search routing. See [Core Direct Lanes](/api-reference/core-direct-lanes) for the data-plane concept. ### Step-wise reflection parameters `POST /v2/control/reflect` now accepts additional fields for targeted reflection: | Field | Type | Description | | ----------------------- | ------ | -------------------------------------------------- | | `step_id` | string | Scope reflection to a specific step | | `last_n_items` | int32 | Reflect only over the N most recent evidence items | | `include_step_outcomes` | bool | Include recorded step outcomes as reflection input | These compose with existing fields (`run_id`, `checkpoint_id`). Use `last_n_items` for incremental after-each-step reflection, and `include_step_outcomes` when step reward signals should influence lesson extraction. ### Activity routes request shapes #### `POST /v2/control/activity` List chronological memory/activity entries. | Field | Type | Required | Description | | ------------- | --------- | -------- | ------------------------------------------------ | | `run_id` | string | yes | Session/run identifier | | `limit` | int32 | no | Maximum entries to return (default 50) | | `offset` | int32 | no | Pagination offset | | `entry_types` | string\[] | no | Filter by entry type (e.g. `["fact", "lesson"]`) | | `agent_id` | string | no | Filter by agent | Response: `{ "entries": [...], "total": int, "has_more": bool }`. #### `POST /v2/control/activity/export` Export activity as JSONL. | Field | Type | Required | Description | | ------------- | --------- | -------- | -------------------------------- | | `run_id` | string | yes | Session/run identifier | | `format` | string | no | Export format, default `"jsonl"` | | `entry_types` | string\[] | no | Filter by entry type | Response: JSONL stream of activity entries. #### `POST /v2/control/activities/append` Append manual activity traces. | Field | Type | Required | Description | | ------------------- | --------- | -------- | --------------------------------------------- | | `run_id` | string | yes | Session/run identifier | | `entries` | object\[] | yes | Activity entries to append | | `entries[].type` | string | yes | Entry type (e.g. `"observation"`, `"action"`) | | `entries[].content` | string | yes | Entry content | | `agent_id` | string | no | Agent that produced the entries | Response: `{ "appended": int }`. ### Run management request shapes #### `POST /v2/control/runs/link` | Field | Type | Required | Description | | --------------- | ------ | -------- | --------------------- | | `run_id` | string | yes | Parent run identifier | | `linked_run_id` | string | yes | Child run to link | Response: `{ "linked": true }`. #### `POST /v2/control/runs/unlink` | Field | Type | Required | Description | | --------------- | ------ | -------- | --------------------- | | `run_id` | string | yes | Parent run identifier | | `linked_run_id` | string | yes | Child run to unlink | Response: `{ "unlinked": true }`. #### `POST /v2/control/delete_run` | Field | Type | Required | Description | | -------- | ------ | -------- | ------------- | | `run_id` | string | yes | Run to delete | Response: `{ "deleted": true }`. #### `POST /v2/control/context/snapshot` | Field | Type | Required | Description | | ------------------------ | ------ | -------- | ---------------------------- | | `run_id` | string | yes | Session/run identifier | | `include_working_memory` | bool | no | Include working memory state | | `include_goals` | bool | no | Include active goals | Response: `{ "snapshot": { "working_memory": {...}, "attention": {...}, "goals": [...] } }`. #### `POST /v2/control/ingest/stats` | Field | Type | Required | Description | | -------- | ------ | -------- | ---------------------- | | `run_id` | string | yes | Session/run identifier | Response: `{ "total_ingested": int, "by_type": {...}, "last_ingest_at": string }`. #### `GET /v2/control/ingest/jobs/:job_id` No request body. Returns: | Field | Type | Description | | ----------------- | ------ | ----------------------------------------------------- | | `job_id` | string | Job identifier | | `status` | string | One of `pending`, `processing`, `completed`, `failed` | | `items_total` | int | Total items in the job | | `items_processed` | int | Items processed so far | | `created_at` | string | ISO timestamp | | `completed_at` | string | ISO timestamp (if completed) | ### Managed resources: Projects, Agents, Skills, Prompts Managed MuBit deployments expose a resource model so you can configure agents declaratively instead of re-encoding them in every run. Projects group related agents, each agent owns a versioned system prompt and a set of skills (tools/playbooks), and all of it is versioned with a candidate → active promotion flow. #### Project CRUD | Route | Method | Purpose | Recommended SDK surface | | ----------------------------- | ------ | -------------------- | --------------------------------------------------- | | `/v2/control/projects` | `POST` | Create a project | `client.createProject(...)` / `create_project(...)` | | `/v2/control/projects/list` | `POST` | List projects | `client.listProjects(...)` / `list_projects(...)` | | `/v2/control/projects/get` | `POST` | Get a single project | `client.getProject(...)` / `get_project(...)` | | `/v2/control/projects/update` | `POST` | Rename / describe | `client.updateProject(...)` / `update_project(...)` | | `/v2/control/projects/delete` | `POST` | Delete a project | `client.deleteProject(...)` / `delete_project(...)` | `POST /v2/control/projects` request: | Field | Type | Required | Description | | ------------- | ------ | -------- | ----------------------------------- | | `name` | string | yes | Project name (shown in the console) | | `description` | string | no | Short description | Response: `{ "project": { "project_id": "proj-...", "name": "...", "description": "...", "agent_ids": [], "created_at": "...", "updated_at": "..." } }`. #### Agent Definition CRUD Agents belong to a project and own a role, description, and active system prompt. | Route | Method | Purpose | | ------------------------------------ | ------ | ---------------------------------- | | `/v2/control/projects/agents` | `POST` | Create agent definition | | `/v2/control/projects/agents/list` | `POST` | List agents in a project | | `/v2/control/projects/agents/get` | `POST` | Get a single agent | | `/v2/control/projects/agents/update` | `POST` | Update role / description / prompt | | `/v2/control/projects/agents/delete` | `POST` | Delete an agent | `POST /v2/control/projects/agents` request: | Field | Type | Required | Description | | ----------------------- | ------ | -------- | ------------------------------------------------------- | | `project_id` | string | yes | Owning project | | `agent_id` | string | yes | Stable agent identifier | | `role` | string | no | Short role description | | `description` | string | no | Longer description | | `system_prompt_content` | string | no | Initial prompt — creates the first active PromptVersion | #### Run history for a project | Route | Method | Purpose | | ------------------------------- | ------ | --------------------------------- | | `/v2/control/projects/runs` | `POST` | List run history for a project | | `/v2/control/projects/runs/get` | `POST` | Get a specific run history record | Run history records expose `lessons_extracted`, `prompt_changes`, `avg_outcome_score`, `outcome_count`, and `ingest_count` per run — so you can observe how an agent's behavior evolves. #### Prompt versioning + optimization Every agent has at most one `active` PromptVersion and zero or more `candidate` PromptVersions awaiting approval. The control plane can mint candidates automatically via `optimize_prompt`. | Route | Method | Purpose | | ----------------------------- | ------ | ---------------------------------------------------------- | | `/v2/control/prompt/set` | `POST` | Write a new version (`activate=true` promotes immediately) | | `/v2/control/prompt/get` | `POST` | Get the current active prompt | | `/v2/control/prompt/versions` | `POST` | List all versions for an agent | | `/v2/control/prompt/activate` | `POST` | Promote a candidate to active | | `/v2/control/prompt/optimize` | `POST` | LLM-powered candidate generation from recent outcomes | | `/v2/control/prompt/diff` | `POST` | Diff two versions (returns unified `diff_text`) | Version statuses: `active`, `candidate`, `retired`, `archived`. Sources: `manual`, `optimization`, `rollback`. `POST /v2/control/prompt/optimize` request: | Field | Type | Required | Description | | -------------- | ------ | -------- | ---------------------------------------------------------- | | `agent_id` | string | yes | Agent whose prompt is being optimized | | `project_id` | string | yes | Containing project | | `llm_override` | object | no | `{ provider, model, temperature, max_tokens, timeout_ms }` | Response: `{ success, candidate, optimization_summary, confidence, activated }`. #### Skill CRUD + optimization Skills are named tools or playbooks attached to a project (or optionally to a specific agent). They version identically to prompts. | Route | Method | Purpose | | ----------------------------- | ------ | ------------------------------------------------------- | | `/v2/control/skills` | `POST` | Create skill | | `/v2/control/skills/list` | `POST` | List skills | | `/v2/control/skills/get` | `POST` | Get a skill | | `/v2/control/skills/update` | `POST` | Update definition | | `/v2/control/skills/delete` | `POST` | Delete a skill | | `/v2/control/skills/versions` | `POST` | List versions | | `/v2/control/skills/activate` | `POST` | Promote a candidate skill version | | `/v2/control/skills/optimize` | `POST` | Generate a candidate skill version from recent outcomes | | `/v2/control/skills/diff` | `POST` | Diff two skill versions | `POST /v2/control/skills` request: | Field | Type | Required | Description | | ------------------- | ------ | -------- | ---------------------------------------------- | | `project_id` | string | yes | Owning project | | `agent_id` | string | no | If set, attaches the skill to a specific agent | | `name` | string | yes | Skill name | | `description` | string | no | Short description | | `parameters_schema` | string | no | JSON schema describing parameters | | `instructions` | string | no | Instructions/playbook text | | `skill_type` | string | no | `"tool"` (default) or `"playbook"` | #### Control sessions Sessions are long-lived handles the control plane uses to coordinate multi-turn agent work. In typical SDK usage, `run_id` serves the same purpose; sessions are exposed for tools that need explicit lifecycle control. | Route | Method | Purpose | | ----------------------------- | ------ | -------------------------------------------------------- | | `/v2/control/sessions/create` | `POST` | Create a control session | | `/v2/control/sessions/get` | `POST` | Fetch session state (phase, ingest count, last activity) | | `/v2/control/sessions/close` | `POST` | Close a session | ### Platform health endpoints These unauthenticated routes live at the root of each MuBit node (alongside `/v2/core/*` and `/v2/control/*`). Use them for Kubernetes probes, load-balancer checks, and operational monitoring — **not** from application code. | Route | Method | Purpose | Typical use | | ----------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- | | `/livez` | `GET` | Liveness: the process is running and its HTTP stack is responsive. Always returns 200 while the server accepts connections. | Kubernetes `livenessProbe`; triggers pod restart on failure. Keep the threshold forgiving — a busy node should not be killed. | | `/readyz` | `GET` | Readiness: the node is ready to serve real traffic. Returns 503 while background ACL and sparse-index rebuilds are still running (reads would see stale state in that window). | Kubernetes `readinessProbe` and load-balancer health check; a 503 temporarily removes the node from rotation without restarting it. | | `/v2/core/health` | `GET` | Coarse legacy health probe. Unauthenticated. Returns the literal string `"OK"` when the HTTP layer is up. | External checks that only need "is the endpoint reachable". Prefer `/livez` + `/readyz` for K8s. | | `/metrics` | `GET` | Prometheus scrape endpoint — includes `mubit_llm_calls_total`, `mubit_llm_tokens_total`, `mubit_llm_call_duration_seconds`, storage and ingest counters. | Prometheus / Grafana scraping. | #### `GET /livez` **Response:** `200 OK` with body `ok`. Liveness is intentionally cheap. It does not query storage, Redis, or any downstream dependency — that is the job of `/readyz`. A failing `/livez` means the process is wedged and should be restarted. #### `GET /readyz` **Responses:** * **Ready:** `200 OK` ```json { "status": "ready" } ``` * **Not ready:** `503 Service Unavailable` ```json { "status": "not_ready", "reason": "acl_rebuild_in_progress" } ``` The node flips to ready once the initial ACL warmup, sparse-index prewarm, and partitioned sparse prewarm complete. During that window, the process accepts connections (so `/livez` stays 200) but returns 503 from `/readyz` so the load balancer can route around it. Typical `reason` values: `starting`, `acl_rebuild_in_progress`, `sparse_prewarm_in_progress`. Treat `reason` as a diagnostic hint — it is advisory, not API-stable. The platform-api service (which fronts the console and the managed-instance API) exposes its own `/healthz` on port 8080. That is a separate surface from the data-plane `/livez`/`/readyz` probes above, which sit on each instance's port 3000. ### Next steps * Compare the proto surface at [Control gRPC reference](/api-reference/control-grpc). * Review explicit planning routes at [State management endpoints](/api-reference/state-management). import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## Core Direct Lanes and Policy Policy contract for direct core query routes and safe rollout guidance. Direct core routes expose low-level retrieval behavior and are intentionally policy-gated. ### Route policy contract Always allowed: * `/v2/core/health` * `/v2/core/auth/*` Policy-gated: * `POST /v2/core/search` requires `MUBIT_CORE_ENABLE_DIRECT_SEARCH` Other external `/v2/core/*` routes are denied by default middleware. ### Core route reference These routes are available at `/v2/core/*`. Routes beyond auth and health require appropriate policy flags or internal access. #### Data operations | Route | Method | Purpose | | ----------------------- | -------- | ------------------------------------- | | `/v2/core/insert` | `POST` | Insert a single node | | `/v2/core/batch_insert` | `POST` | Batch insert multiple nodes | | `/v2/core/node/:id` | `GET` | Retrieve a node by ID | | `/v2/core/node/:id` | `DELETE` | Delete a node by ID | | `/v2/core/runs/:run_id` | `DELETE` | Delete all nodes in a run | | `/v2/core/search` | `POST` | Semantic vector search (policy-gated) | #### Agent scratchpad memory Per-session key-value scratchpad for agent working memory. | Route | Method | Purpose | | ----------------------------- | -------- | --------------------------- | | `/v2/core/memory/:session_id` | `POST` | Write to session scratchpad | | `/v2/core/memory/:session_id` | `GET` | Read session scratchpad | | `/v2/core/memory/:session_id` | `DELETE` | Clear session scratchpad | #### SDM (Sparse Distributed Memory) operations | Route | Method | Purpose | | -------------------- | ------ | --------------------------- | | `/v2/core/sdm/write` | `POST` | Write to SDM address space | | `/v2/core/sdm/read` | `POST` | Read from SDM address space | SDM provides associative memory with graceful degradation — partial address matches return blended results weighted by Hamming proximity. #### Session management Transactional sessions for atomic multi-operation commits. | Route | Method | Purpose | | ------------------------------- | -------- | ------------------------------------- | | `/v2/core/session/create` | `POST` | Create a new session | | `/v2/core/session/:id/commit` | `POST` | Commit session changes | | `/v2/core/session/:id/drop` | `DELETE` | Drop session without committing | | `/v2/core/session/:id/snapshot` | `POST` | Snapshot current session state | | `/v2/core/session/load` | `POST` | Load a previously snapshotted session | #### Storage maintenance | Route | Method | Purpose | | -------------------------- | ------ | --------------------------------------------------- | | `/v2/core/storage/stats` | `GET` | RocksDB storage statistics (size, keys, compaction) | | `/v2/core/storage/compact` | `POST` | Trigger manual compaction | #### ACL (Access Control) | Route | Method | Purpose | | --------------------- | ------ | -------------------------------- | | `/v2/core/acl/grant` | `POST` | Grant permission to a user/agent | | `/v2/core/acl/revoke` | `POST` | Revoke a permission | | `/v2/core/acl/check` | `POST` | Check if a permission is granted | #### PubSub Data-plane subscriptions fire on `Node` insert / update / delete and on session memory writes. Filters are `all`, `node` (specific `node_id`), `semantic` (vector similarity over `query_text` with a `threshold`), or `session` (session scope). | Route | Method | Response | Purpose | | ----------------------------- | ------ | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `/v2/core/pubsub/subscribe` | `POST` | `text/event-stream` (SSE) | Open a server-sent-event stream. First event is `subscribed` with `{subscription_id}`; subsequent events are `node.inserted`, `node.updated`, `node.deleted`, or `memory.added` with flat JSON payloads (see schema below). The subscription lives for the lifetime of the HTTP connection and is cleaned up automatically on disconnect. | | `/v2/core/pubsub/unsubscribe` | `POST` | JSON | Explicitly remove a subscription by ID (primarily an admin/debugging surface — normal clients just close the SSE connection). | | `/v2/core/pubsub/list` | `POST` | JSON | List active subscription IDs for the calling user, plus the server-wide total. | gRPC equivalent: `CoreService.Subscribe(CoreSubscribeRequest) → stream PubSubEvent` on the same data-plane gRPC surface. Request body for `/v2/core/pubsub/subscribe`: | Field | Type | Required | Description | | ------------- | ------ | -------------------------------- | ------------------------------------------------------------------------------------------------------- | | `filter_type` | string | yes | `"all"`, `"node"`, `"semantic"`, or `"session"`. | | `node_id` | uint64 | when `filter_type == "node"` | Node to watch. | | `query_text` | string | when `filter_type == "semantic"` | Encoded server-side; events fire when an inserted/updated node's vector exceeds similarity `threshold`. | | `threshold` | float | no | Cosine-similarity floor for semantic filters. Defaults to `0.8`. | Event payload schema (each SSE `data:` line + each gRPC `PubSubEvent` message decode to the same JSON shape): | `type` | Populated fields | | -------------------------------- | ---------------------------------------------------------------- | | `subscribed` | `subscription_id` | | `node.inserted` / `node.updated` | `node_id`, `run_id`, `metadata_json`, `created_at`, `updated_at` | | `node.deleted` | `node_id` | | `memory.added` | `session_id`, `entry_json` | `metadata_json` and `entry_json` are JSON-encoded strings on the wire. The official SDK wrappers parse them into native `metadata` / `entry` objects, coerce `uint64` / `int64` fields to numbers, and drop irrelevant proto defaults before yielding the event. Clients speaking the raw HTTP / gRPC surface need to do the same translation themselves. ### Control parity rule The same policy gates apply when `control.query` runs in direct bypass mode. ### Rollout guidance 1. Start with both direct lane flags disabled. 2. Enable one lane at a time outside production. 3. Monitor lane-specific request/error metrics. 4. Keep routed mode fallback active. ### Failure modes and troubleshooting | Symptom | Root cause | Fix | | ------------------------------- | ------------------------------- | ------------------------------------------------ | | Direct lane request denied | Required flag disabled | Use routed retrieval or enable lane explicitly | | Increased production error rate | Lane enabled without guardrails | Roll back lane and re-enable with staged traffic | | Unclear fallback behavior | No route policy decision tree | Define mode fallback rules per feature | ### Next steps * Review integration guardrails at [Data guardrails](/sdk/data-guardrails). * Review retrieval implementation choices at [Retrieve data](/sdk/retrieve-data). import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## Errors HTTP status codes, error types, and structured error payloads returned by the MuBit control API. This page is a stub. The error taxonomy below reflects the current SDK surface; the canonical spec lives in `/api-reference/control-http` once the OpenAPI source is wired up. File issues at github.com/mubit-ai for gaps. Every error response carries a JSON body of the form: ```json { "error": { "type": "rate_limited", "code": "rate_limit_exceeded", "message": "You have exceeded the per-minute write quota for instance us-east-1.", "request_id": "req_018f2c1a-…", "retry_after_ms": 1200 } } ``` `request_id` is included on every response (success or error) — log it. `retry_after_ms` is present only on retryable errors. ### Status codes | Status | Meaning | Retryable? | | --------------------- | --------------------------------------------------------------------------- | ------------------------------ | | `400` | Validation error — the request shape was rejected | No | | `401` | Missing or invalid `Authorization` header | No | | `403` | API key lacks the scope required for this call | No | | `404` | Session, agent, reference id, or job id does not exist | No | | `409` | Conflict — concurrent write to a unique resource | Yes (with backoff) | | `422` | Semantic error — the request shape was valid but business rules rejected it | No | | `429` | Rate-limited | Yes (respect `retry_after_ms`) | | `499` | Client closed the request before the server finished | No | | `500` | Unhandled server error | Yes (with backoff) | | `502` / `503` / `504` | Upstream / overloaded / timeout | Yes (with backoff) | ### Error types | `error.type` | When it happens | | ---------------------- | ------------------------------------------------------- | | `validation_error` | Required field missing or wrong shape | | `authentication_error` | Bad key, revoked key, expired key | | `permission_error` | Key valid, but lacks scope (e.g. `archive_block` write) | | `not_found` | Lookup by id returned nothing | | `conflict` | Concurrent write hit a uniqueness constraint | | `rate_limited` | Too many requests for this instance/key/route | | `unprocessable` | Business-rule rejection (e.g. malformed memory entry) | | `server_error` | MuBit bug — file an issue with the `request_id` | | `upstream_error` | Storage backend is unavailable; retry | ### SDK exception mapping The SDK wraps each error type as a typed exception: ```python import mubit try: client.remember(...) except mubit.errors.RateLimited as e: time.sleep(e.retry_after_ms / 1000) # retry except mubit.errors.PermissionError as e: # add the missing scope to register_agent — don't retry raise except mubit.errors.ValidationError as e: # fix the call shape raise ``` See [Retries and idempotency](/sdk/retries) for the recommended retry strategy. import { CardGroup, Card, Note, Warning, Tip, Steps, Step, Frame, Accordion, Tabs, Tab, MubitTerminal, PageHeader } from '@components' ## State Management Surface Run-scoped state routes for variables and concepts, plus deprecated planning state (goals / actions / cycles). MuBit is primarily a memory engine. It also exposes a small set of **run-scoped state routes** (variables, concepts) that are useful when you want to persist orchestrator state next to memory. A broader planning surface (goals, actions, decision cycles) is still wired for backward compatibility but is **deprecated** — most workflows are better served by external task / orchestration systems. For most use cases, start with `remember`, `recall`, `getContext`, `checkpoint`, and the learning-loop APIs. Reach for these state routes only when memory alone isn't enough. ### Supported state surfaces | Domain | Purpose | Status | | --------- | ------------------------------------------------------------------ | ------------- | | Variables | Small explicit run-scoped state values that memory shouldn't infer | **Supported** | | Concepts | Named planner concepts / controlled vocabulary | **Supported** | #### Variables routes | Route | Method | Purpose | | ------------------------------ | ------ | ------------------------- | | `/v2/control/variables/set` | `POST` | Set a run-scoped variable | | `/v2/control/variables/get` | `POST` | Get a variable | | `/v2/control/variables/list` | `POST` | List variables for a run | | `/v2/control/variables/delete` | `POST` | Delete a variable | #### Concepts routes | Route | Method | Purpose | | ----------------------------- | ------ | ---------------- | | `/v2/control/concepts/define` | `POST` | Define a concept | | `/v2/control/concepts/list` | `POST` | List concepts | ### Deprecated planning surfaces The goals, actions, and decision-cycle RPCs are **deprecated**. They remain wired for existing deployments but are no longer recommended. * **Goals** — Track tasks in a dedicated system (Linear, Jira, Asana). MuBit should store the lessons and outcomes, not the task list. * **Actions / decision cycles** — Delegate to your agent orchestration framework (LangGraph, CrewAI, ADK). Persist only the resulting lessons, outcomes, and traces in MuBit. If you're currently using these routes, keep them running — they aren't being turned off — but plan to migrate the planning surface to external tools and keep MuBit focused on experiential memory. | Capability | Routes | Replacement | | --------------- | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Goals | `/v2/control/goals/add`, `/v2/control/goals/update`, `/v2/control/goals/list`, `/v2/control/goals/tree` | External task tracker (Linear, Jira). Store only the resulting **lessons / rules / mental models** in MuBit. | | Actions | `/v2/control/actions/submit`, `/v2/control/actions/log` | Agent framework execution log (LangGraph, CrewAI). Use [archive + dereference](/api-reference/control-http#core-memory-routes) when you need exact trace fidelity. | | Decision cycles | `/v2/control/cycles/run`, `/v2/control/cycles/history` | Agent framework planning loop. Record the **outcome** of each cycle via `record_outcome` / `record_step_outcome`. | ### Recommended usage guidance * Keep variables and concepts in the same `run_id` as the memory they explain. * Move goals, actions, and decision cycles to your task / orchestration system of record. * Record the learning signal that comes out of those systems (outcomes, lessons, rules) in MuBit so future runs benefit. * Use `checkpoint` when a long run is about to compact or cross a risky boundary. ### Failure modes and troubleshooting | Symptom | Root cause | Fix | | ------------------------------------------------------- | --------------------------------- | -------------------------------------------------------------------------------------- | | Planner state exists but the model still forgets things | Only state was stored, not memory | Persist facts, lessons, or rules alongside variables | | Debugging is hard | Actions / cycles are missing | Record planner decisions in your orchestration framework and surface outcomes to MuBit | | Variable list grows unbounded | No TTL discipline | Use `variables/delete` at end-of-run or namespace by session | ### Next steps * Review the main control surface at [Control HTTP reference](/api-reference/control-http). * Use planning plus memory in [Stateful task trees](/recipes/stateful-task-trees).