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 | client.memory.remember() for normal writes, client.raw.invoke("control.ingest", ...) when you need job control |
/v2/control/batch_insert | POST | Compatibility bulk write path | client.raw.invoke("control.batch_insert", ...) |
/v2/control/ingest/jobs/:job_id | GET | Ingest job status and completion | client.jobs.get(job_id, run_id=...) (an IngestJob: done, status, error) |
/v2/control/query | POST | Answer-oriented routed retrieval | client.memory.recall() or client.raw.invoke("control.query", ...) |
/v2/control/context | POST | Pre-assembled context block with disclosure modes and budget control | client.memory.context(task, lane="legacy", ...) |
/v2/control/activity | POST | Chronological memory/activity browse surface with scope and type filters | client.audit.run_history() |
/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 | client.memory.archive() |
/v2/control/dereference | POST | Fetch exact stored content by reference_id | client.memory.dereference() |
/v2/control/diagnose | POST | Failure-path lesson surfacing | client.memory.diagnose() |
/v2/control/memory_health | POST | Memory quality, staleness, contradictions, and section health | client.memory.health() |
/v2/control/reflect | POST | Extract lessons and rules from run evidence | client.lessons.reflect() |
/v2/control/lessons | POST | List stored lessons for a run/session scope | client.lessons.transitions(run_id=...); the lesson set is client.lessons.list() (/v2/loop/lessons) |
/v2/control/lessons/delete | POST | Delete a stored lesson | client.memory.forget() / client.lessons.delete() |
/v2/control/lessons/resolve_promotion | POST | Accept or refuse a lesson's pending scope promotion (proposed / auditioning / quarantined) | client.raw.invoke("control.resolve_lesson_promotion", ...) |
/v2/control/lessons/update | POST | Set a lesson's scope, validation_status, stale, or confidence (absolute, clamped to [0, 1]) directly (audited on the entry) | client.raw.invoke("control.update_lesson", ...) |
/v2/control/ingest and /v2/control/batch_insert accept at most 1000 items per request; larger requests return HTTP 400 (InvalidArgument). Chunk bigger writes client-side.
MAS and learning-loop routes
| Route | Method | Purpose | Recommended SDK surface |
|---|---|---|---|
/v2/control/agents | POST | List registered agents for the run | client.agents.list() |
/v2/control/agents/register | POST | Register an agent identity and its scopes | client.agents.register() |
/v2/control/checkpoint | POST | Save a pre-compaction or pre-transition checkpoint | client.checkpoint() |
/v2/control/outcome | POST | Reinforce a lesson or rule after success/failure/partial outcome | client.outcomes.record(); mubit.outcome() for the current run |
/v2/control/step_outcome | POST | Record a per-step process reward signal within a run | client.runs.steps.record(); step.outcome() inside mubit.step() |
/v2/control/strategies | POST | Surface repeated patterns from stored lessons and rules | client.lessons.strategies() |
/v2/control/handoff | POST | Persist a coordination handoff between agents | client.agents.handoffs.create() |
/v2/control/feedback | POST | Persist a response to a handoff or review request | client.agents.handoffs.feedback() |
Explicit planning and state routes
The goals, actions, and decision-cycle routes are deprecated — track tasks in your orchestration framework (LangGraph, CrewAI) or a task system (Linear, Jira) and persist only the resulting lessons and outcomes in Mubit. The variables and concepts routes remain supported. All of these are HTTP/gRPC-only — none are exposed as typed SDK helpers. See State management.
| 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 | client.runs.history(...) |
/v2/control/runs/link | POST | Link a child run to a parent run | client.runs.link(...) |
/v2/control/runs/unlink | POST | Unlink a previously linked run | client.runs.unlink(...) |
/v2/control/delete_run | POST | Delete a run and its associated data | client.runs.delete(run_id) |
/v2/control/agents/heartbeat | POST | Agent liveness heartbeat signal | client.raw.invoke("control.agent_heartbeat", ...) |
/v2/control/activities/append | POST | Append an activity record to a run | HTTP / gRPC only — no SDK helper |
/v2/control/context/snapshot | POST | Full context snapshot including working memory and attention | client.raw.invoke("control.context_snapshot", ...) |
/v2/control/events/subscribe | GET | Stream control events via SSE; empty/absent run_id = every run the actor owns | client.audit.subscribe(...) |
/v2/control/ingest/stats | POST | Get per-run ingest statistics | client.raw.invoke("control.get_run_ingest_stats", ...) |
Operating guidance
- Prefer the
Clientnamespaces (and the global helpers inside a run) for normal application code. - Use
client.raw.invoke(op, payload)when you need exact request-shape control, ingest-job polling, or contract debugging;opis the contract name (control.<route>). - Treat
/v2/control/contextas the default compaction-safe context assembly surface for long-running systems. - Treat
/v2/control/activityas the chronological audit surface for stored Mubit data. It is separate from semanticquery. - Use
/v2/control/archiveplus/v2/control/dereferencewhen later steps need exact artifact fidelity rather than only semantic discovery. - Use
client.memory.diagnose,client.memory.health, andclient.lessons.strategiesbefore 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, orworking_memoryreference_id: stable exact-reference ID when availablereferenceable: whether the source can be dereferenced laterorigin_entry_type: typed LTM source such asfact,lesson,rule,archive_block, orcheckpoint
The /v2/control/query response also includes a citations array — 0-based indices into evidence marking which items grounded final_answer (empty when the answer cites no specific evidence or was an abstention). Indices are validated server-side against the evidence length, so every value is a valid index into evidence. Use them to render citations or audit answer grounding.
/v2/control/context also reports context-pressure telemetry:
- requested token budget
budget_usedbudget_remaining- source counts by entry type
- source counts by retrieval mode
evidence_candidates_consideredevidence_dropped_by_budgetexact_references_surfaced
Run-level outcome request shape
POST /v2/control/outcome reinforces a lesson or rule after a success/failure/partial outcome. Beyond the core fields (reference_id, outcome, signal, rationale, agent_id, user_id, verified_in_production), the wire request accepts two attribution fields:
| Field | Type | Required | Description |
|---|---|---|---|
entry_ids | string[] | no | Additional contributing memory entry IDs for multi-entry attribution. Each is resolved within the run and scope-checked; unknown or out-of-scope IDs are skipped. The primary reference_id is never double-counted if it also appears here |
idempotency_key | string | no | Client-supplied dedup token. When set, the outcome is applied at most once for the token — a retry with the same key (e.g. after a transport timeout) returns success WITHOUT re-applying the signal, so reinforcement counts aren't double-counted. A wire-level field; it is not exposed on client.outcomes.record() |
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. To produce step-attributed lessons, set include_step_outcomes on the reflect request. This is a wire-level ReflectRequest field (not a kwarg on client.lessons.reflect()): send it through the raw operation, e.g. client.raw.invoke("control.reflect", {"run_id": ..., "include_step_outcomes": True}) (Python) or client.raw.invoke("control.reflect", { run_id, include_step_outcomes: true }) (JS).
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 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 |
lessons | ReflectLesson[] | Bring-your-own extractor: when non-empty, skip the built-in reflection agent and run the validation gate, persistence, promotion ladder and events over these lessons instead. Same shape as the response lessons (content, lesson_type, scope, importance, conditions, rationale); lesson_id is ignored. Empty lesson_type/scope/importance default to observation/run/medium; unknown values are rejected |
confidence | float | Batch confidence the validation gate scores lessons with, clamped to [0,1]; absent = 1.0. Only read when lessons is non-empty |
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. Use lessons when your own agent already extracted the lessons and you want them gated, stored and promoted exactly as reflected ones are — the response decisions are the same, and stored rows carry auto_extracted: false.
Activity routes request shapes
POST /v2/control/activity
List chronological memory/activity entries.
| Field | Type | Required | Description |
|---|---|---|---|
run_id | string | no | Session/run scope. When empty, lists activity across runs visible to the actor |
user_id | string | no | Logical user scope |
agent_id | string | no | Filter by agent |
entry_types | string[] | no | Filter by entry type (e.g. ["fact", "lesson"]) |
created_after | string | no | Inclusive lower-bound timestamp (RFC3339) |
created_before | string | no | Inclusive upper-bound timestamp (RFC3339) |
sort | string | no | "desc" (default, newest first) or "asc" |
limit | uint32 | no | Page size. Defaults to 100 and is clamped to [1, 500] — an omitted or 0 limit resolves to 1, not 100 |
page_token | string | no | Opaque pagination cursor returned as next_page_token from the previous page (currently a numeric offset string) |
exclude_derived | bool | no | When true, exclude promoted/derived facts from the listing |
projection | string | no | "full" (default) or "compact" (truncates content to 200 chars and strips verbose metadata) |
Response: { "entries": [...], "next_page_token": "...", "total_visible": int }. next_page_token is empty when the last page has been reached.
POST /v2/control/activity/export
Export activity as JSONL. The request takes the same filter fields as the browse call — there is no format request field; the response is always JSONL.
| Field | Type | Required | Description |
|---|---|---|---|
run_id | string | no | Run scope (same filter semantics as /v2/control/activity) |
user_id | string | no | Logical user scope |
agent_id | string | no | Filter by agent |
entry_types | string[] | no | Filter by entry type |
created_after / created_before | string | no | Inclusive RFC3339 bounds |
sort | string | no | "desc" (default) or "asc" |
Response: { "format": "jsonl", "content": "<serialized entries>", "entry_count": int }.
POST /v2/control/activities/append
Append a manual activity trace. Each request appends a single activity record — issue one request per record.
| Field | Type | Required | Description |
|---|---|---|---|
run_id | string | yes | Run identifier |
agent_id | string | no | Agent that produced the record |
activity | object | yes | The activity record to append |
activity.type | string | yes | Record type (e.g. "observation", "action") |
activity.payload | string | yes | Record content |
activity.tool_name / activity.input_ref / activity.output_ref / activity.ts | string | no | Optional provenance fields |
Response: { "success": true, "event_id": "evt_..." }.
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 |
run_id | string | Run the job belongs to |
status | string | One of queued, running, completed, failed |
done | bool | True once the job reached a terminal state — poll on this |
error | string | Error message when status == "failed" |
created_at / started_at / finished_at | string | ISO timestamps for the job lifecycle |
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.projects.create(...) |
/v2/control/projects/list | POST | List projects | client.projects.list(...) |
/v2/control/projects/get | POST | Get a single project | client.projects.get(...) |
/v2/control/projects/update | POST | Rename / describe | client.projects.update(...) |
/v2/control/projects/delete | POST | Delete a project | client.projects.delete(...) |
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 }. timeout_ms is clamped server-side to [1000, 600000] ms (10 min); out-of-range values are silently adjusted |
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{ "status": "ready" } - Not ready:
503 Service Unavailable{ "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.
- Review explicit planning routes at State management endpoints.