Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Loop console

The project pages of the console show what the SDK sends and what the server does with it: runs and their injections, the lesson set, the policy in force, and the audit log. Every loop page is scoped to one project and one environment; the same scope the SDK resolves from MUBIT_PROJECT and MUBIT_ENV.

Each page is a view over the loop routes (/v2/loop/*) that the SDK namespaces and the mubit CLI also use, so what you see in the console is what mubit status, client.lessons.list() or client.audit.list() return.

Project header

Every project page shares one header: project name, instance phase pill, the environment switcher, the loop status pill, and the Connect and Settings buttons. Below it, the project navigation has two groups, with Settings at the right end of the same bar:

GroupTabs
Learning loopOverview · Runs · Lessons · Proposals · Policy · Snapshots · Jobs · Audit · Injections · Compare
ConfigureAgents · Skills · Memory · Sandbox · Logs

The Runs, Lessons, Injections, Audit and Compare pages keep their filters in the page URL and offer a Views menu to save the current filter set under a name. Saved views are stored in the browser (per organisation, in localStorage): they do not follow you to another machine and are not shared with teammates.

The status pill reads, for the selected environment, policy starter@1:55af.. (policy version) · set v42 (lesson-set version) · gate auto (gate mode) · a kill dot. Clicking it opens the Policy tab. The values come from GET /v2/loop/policy and GET /v2/loop/status; the policy version is the one mubit doctor prints on its mode: line.

Environment switcher

Lessons are partitioned by environment. The switcher shows one chip per environment the project knows: dev and prod are seeded for every project, plus any environment that already has lessons, snapshots or transitions (its tooltip says it was seen in data but is not in the project's list). The selected environment is kept in the page URL (?env=) and mirrored to the browser for the next visit, so a link to a page opens the same environment.

  • Switching re-queries every scoped list on the page.
  • Protected environments (prod by default) render with a warning tint and the word "protected". Writes on a protected environment ask for the environment name typed back and a reason; policy edits also require a reason in the review step. Promote and restore ask for the target environment name typed back on every environment.
  • A run written with a different MUBIT_ENV than the one selected does not appear. If the Runs tab is empty, check the chip first, then the scope: line of mubit doctor.

Kill switch

The kill switch stops injection for the whole instance. While it is on, a banner appears above every project page:

LOOP KILL SWITCH ON
set from the console or API by key:admin/alice 3 min ago · reason: rollout incident.
Context requests return no units and no lessons are extracted while it is on.
                                                        [View audit]  [Turn off]
FieldMeaning
ontrue while injection is disabled
sourcestored when set through the console, CLI or SDK; env when the instance forces it with MUBIT_CL_LOOP_KILL=1; off otherwise
actor, ts, reasonWho set it, when, and why

Effects while on: context() answers killed: true with no units (the SDK prints WARNING mubit: kill switch is on for env=dev (reason: "…"); injection disabled. once per process and sets ContextBlock.reason = "killed"); events are still accepted; no distillation is scheduled; expired jobs do not fall back to the builtin distiller.

Turning it on or off needs the admin principal (see Access); the button is disabled with the tooltip "needs admin" for everyone else. When source is env, the banner reads "forced by MUBIT_CL_LOOP_KILL on the instance; cannot be changed here" and has no Turn off button; the API answers 409 kill switch is forced on by MUBIT_CL_LOOP_KILL. Every change is an audit record of kind kill.set.

The same switch from the CLI and the SDK:

mubit kill on --reason "rollout incident"
mubit kill status
mubit kill off
client.kill.set(on=True, reason="rollout incident")
client.kill.get()          # {"on": True, "source": "stored", "reason": "...", "actor": "key:...", "ts": "..."}

Overview

Answers "is the loop on, is it learning, is anything stuck" for the selected environment and a time range (24h, 7d or 30d, kept in the URL as ?range=).

  • KPI row for the range: Runs, Injected runs (runs with at least one injection), Active lessons, Pending lessons (waiting for the gate or a review), Open proposals (with the oldest and soonest-expiring), Queue depth (queued · leased · not ready; shown when external jobs apply), Degraded rate (injections that fell back).
  • Kill switch card: on, source, actor, ts, reason; Turn on / Turn off (admin).
  • Policy card: policy_version, gate (with a "fixed by env" marker when the instance pins it), distill, budget, credit, lesson set version; Open goes to Policy.
  • Loop health: events received, reflections (scheduled and finished), the last distill trigger and its time, events until the next trigger, negative streak, last event.
  • Queue card: job counts queued / leased / completed / failed / expired, the "not ready" backlog and the oldest queued age; shown when distill.owner or gate.mode is external, or when any job exists (managers and admins; workers see "visible to managers and admins").
  • Charts for the range: "Runs and injections" and "Lessons stored, injected, credited" as stacked bars, plus metric cards. Markers on the time axis show policy.set, snapshot.restore and env.promote records so a step change lines up with the control-plane change that caused it.
  • Live feed: one row per injection receipt (hit / empty / degraded / killed) and per outcome record, newest first.

Actions: Kill on/off (admin), Take snapshot (manager). Data: GET /v2/loop/status, /kill, /policy, /jobs, /proposals, /lessons, /audit.

Before the first event arrives the page shows the first-run listener in its listening state with the snippet; with events but no lessons it shows "Runs are arriving. Lessons appear after the first run ends."

Runs

One row per loop run, with the trace behind it.

ColumnSource
run id, agent, started, durationrun.start / run.end events
steps, events (model / tool / note)event counts by kind
injectionsreceipt count; distinct units on hover
outcomerun-level label and signal chip; step signals as a sparkline
lessons outstored / pending from this run
thread, env, statusopen, ended, reflecting, degraded, killed

Filters are typed chips in one search bar and are kept in the URL. Server-side chips: agent:, session: (also typed as thread: or session_id:), user:, since:24h, until:, injected:yes|no, outcome:. Client-side chips over the loaded rows: signal:>0.5, degraded:true, killed:true, has:run.end, lesson_set_version:, policy_version:, source:, status:, and free text over task and notes. A summary panel on the right totals the current filter (runs, events, injections, outcomes, mean signal, lessons stored, reflection latency p50/p95). "Live" polls for new rows every 5 s; "Export JSONL" downloads the filtered rows in windows; the Views menu saves the current chips in the browser.

The SDK prints a link to the run at MUBIT_LOG=info when MUBIT_CONSOLE_URL is set (mubit: run run-d773cd4c ended (2 events, 1 reflection scheduled) https://…/app/r/run-d773cd4c), and mubit runs open <run_id> prints the same URL. The agent's Runs tab is the same table with agent: locked.

Run detail

Opening a run shows which lessons were injected at which step, what outcome followed, and what the run produced.

  • Header: run id, agent, env/project, thread, user, started → ended, outcome label and signal, injections (units), lessons out, lesson_set_version, policy_version, degraded / killed pills.
  • Step rail (left): steps in first-seen order, unstepped events under "(no step)", and a trailing "reflection" entry with the last distillation (trigger, elapsed, lessons stored, degraded).
  • Timeline (centre): four lanes of point events: events, injection receipts, outcomes, reflection.
  • Detail panel (right): the selected event as messages or raw JSON; a receipt with its units table (text · kind · scope · state · confidence · why.score · retrieval_mode), budget bar, excluded counts and the system_text that was sent; an outcome with the credited units (explicit and fan-out) and each unit's confidence before → after.
  • Tabs: Attribution, Lessons out, Reflection, Raw events (export JSONL; message bodies are redacted unless you show them), Policy.

A run with no loop events (written through the legacy ingest API) says so and links to the Memory tab.

Lessons

The lesson set for the selected project and environment: what client.lessons.list() returns and what context() draws from.

ColumnNotes
content, typeexcerpt; [kind] chip when set
statuspending, active, rejected, retired (retired rows show "active from X to Y")
scope, confidencerun, session, global; value plus the last outcome delta
origin, actor, agentcreate, reflection, external; who wrote it
active since, last changedfrom the transition history
conditionthe lesson's condition code; a warning icon when it has raised errors
proposal, promoted fromchips when an open proposal exists or the lesson was copied from another environment
served, credited, hit ratehow often the lesson was injected and credited by an outcome

Facets (status, type, scope, origin, kind, agent, plus the has-condition, open-proposal and promoted toggles) filter with counts. As of opens the set at an earlier time (GET /v2/loop/lessons?as_of=) with a banner naming the timestamp and lesson-set version; "Compare with now" lists added, removed and changed lessons.

The lesson drawer has five tabs: Overview (content, fields, condition, rationale, scope, status, confidence, origin, source run, promoted-from, proposal); Timeline (every transition: from → to for status, scope and confidence, with reason, actor, principal, policy_version and lesson_set_version; a confidence sparkline above); Evidence (the distill job's evidence for external distillers, otherwise the source run's events); Effectiveness (served, credited, hit rate, signal after injection); History (audit records that name this lesson).

Actions: Add lesson by hand (manager; kind, fields, condition, visibility), Delete (manager; cascade lists the copies in other environments first), Export SKILL.md (GET /v2/loop/skills, read-only with lesson_set_version). CLI: mubit lessons list [--as-of --status --scope --limit], mubit lessons get <id>, mubit lessons rm <id>, mubit lessons transitions [--lesson --run], mubit export skills.

Empty state before the first lesson: "Lessons are extracted when a run ends. A run needs something to learn from: a correction, a tool failure, or an outcome."

Proposals

The review inbox for gated lessons. Proposals exist when gate.mode is propose (the server holds the lesson for a person) or external (a gate job holds it for a worker).

ColumnNotes
lessoncontent excerpt, type, run, agent
changefrom.scope → to.scope and from.status → to.status; the unchanged half is muted
confidence, recurrence, age
expiresexpires_ts; amber under three hours
byactor / origin
gatejob id and state when gate.mode is external

State tabs: Open / Resolved / Expired / Orphaned, with a lesson-type filter. The drawer shows the content, current versus proposed status, scope and confidence, the gate reason, the history, an "Apply at scope" select (run, session, global), a reason field, and Reject / Accept. Keyboard: j / k move between rows, a / r accept or reject, Cmd+Enter accepts, Escape closes the drawer.

Accept and Reject need manager. A proposal cannot be accepted by the actor who proposed it unless the principal is admin (409 proposer cannot approve own proposal); the button is disabled with that text. Bulk accept and reject run one call per row and show per-row results. CLI: mubit proposals list, mubit proposals resolve <id> accept|reject.

Empty state on the open tab: "Nothing to review. Proposals appear when gate.mode is propose or external (currently auto)."

Policy

The effective policy for the selected project and environment, key by key, with where each value comes from.

ColumnNotes
keygrouped by section: capture, distill, gate, inject, attribute
effectivethe value in force
sourcebuiltin (the starter@1 document), env (instance environment), project (stored override), env_final (instance pin that cannot be overridden)
storedthe project-layer value; "shadowed" when a higher layer also sets the key
typeenum values or bounds from the key registry, used by the editor

Edits accumulate in a pending panel. Review & save shows a key: old → new diff, asks for a reason (required on protected environments) and sends one PATCH /v2/loop/policy; the new policy_version appears next to the old one. Validation errors (unknown policy key, invalid value … expected <type>) show on the row. Reset all overrides (DELETE) asks for the environment name.

Compare env opens the compare-and-promote panel, which copies the policy overrides and the active lessons from one environment to another: pick source and target, review the override diff and the lessons that will be copied versus skipped (already promoted), tick policy and/or lessons, give a reason, type the target environment name. A snapshot of the target is taken first; the result banner shows snapshot_before, lessons_copied, lessons_skipped, policy_version_to, lesson_set_version_to, with a link to the audit record.

Actions: edit (manager), reset (manager on the server: DELETE /v2/loop/policy needs manager; the console offers Reset all overrides only to owners and admins), promote (admin). The History panel lists policy.set records with set, unset, reason and policy_version_before → after. CLI: mubit policy get [--layer] [--raw|--flat], mubit policy set K V ..., mubit policy unset K ..., mubit policy reset, mubit policy promote --from dev --to prod [--dry-run], mubit policy diff --from dev --to prod.

Snapshots

Rollback points for the lesson set and the policy overrides of one environment.

ColumnNotes
id, taken, label
lessons, overridescounts captured
set version, policy versionat capture time
by, originactor; snapshots.create (manual) or policy.promote (automatic before a promote)

Take snapshot (manager) asks for a label. Restore (admin) offers three options: restore lessons, restore policy overrides, retire lessons created after the snapshot; shows a preview (will revert / will be retired / missing, from a dry run) and asks for the environment name typed back, plus a reason on a protected environment. Restore does not take a snapshot first; the modal offers "Take a snapshot first". The result banner shows restored, retired, policy_restored, missing, the new lesson_set_version and a link to the snapshot.restore audit record. Compare diffs two snapshots, or a snapshot against now, by lesson id (status, scope, confidence) and by override.

CLI: mubit snapshot create --label "...", mubit snapshot list, mubit snapshot get <id>, mubit snapshot restore <id>. SDK: client.snapshots.create(label=...), client.snapshots.restore(id, lessons=True, policy=True, retire_new=True).

Empty state: "No snapshots for dev. Take one before changing policy or promoting; promote takes one automatically."

Jobs

The queue for external distill and gate workers. Jobs exist when distill.owner or gate.mode is external.

ColumnNotes
id, kind, statequeued, leased (with a lease countdown), completed, failed, expired
scope, run / agentproject/env
attemptattempts / max_attempts
lease, age / TTLcountdown to lease_expires_ts; "not ready" while ready is false; countdown to expires_ts while queued
worker, last error, proposalclaimed_by on hover; the proposal a gate job decides

The header shows the instance-wide counts. Filters: kind, state, run id (all server-side, kept in the URL). Expanding a row shows the payload (evidence for distill, proposal summary for gate), the result (stored, rejected with reasons, clamped), the policy_version, and the job's audit records (job.enqueue, job.claim, job.fail, job.complete, job.expire). A note says when the builtin distiller will take over if the job fails again (on_timeout=builtin).

Two operator actions (manager): Release clears the lease of a leased job so another worker can claim it (a reason is recorded in the audit log); Terminate fails a job that has not ended, with a required reason that is stored as the job error. Heartbeat and complete stay with the lease holder. Workers: client.jobs.work(handler) or mubit jobs work; mubit jobs list|claim|get|complete|fail|heartbeat for one-offs.

Empty state: "No jobs. Jobs exist when distill.owner or gate.mode is external (currently builtin / auto)."

Audit

The governance log: every control-plane write, every injection receipt and every outcome record.

ColumnNotes
timerelative and absolute UTC
kindlesson.transition, lesson.delete, policy.set, kill.set, job.*, proposal.*, snapshot.create, snapshot.restore, env.promote, injection.receipt, outcome.record, …
objectthe first of lesson_id, proposal_id, job_id, snapshot_id, injection_id, outcome_id, from_env → to_env
actor / principalkey:<key_id>/<actor> and worker / manager / admin
summaryper-kind text; second line policy_version before → after or lesson_set_version

Filters: since / until / kinds (server), object id / actor / principal (client, over the loaded window). The default window is the last 24 hours with up to 500 records per page; "Load older" extends it. "Live" polls for new records every 10 s and is paused while an until bound is set. Export JSONL downloads the since → until window (the last 24 hours by default): the export is fetched in windows of at most 5000 records, splitting a window that fills up, and the banner says when a window still returned 5000 records at the smallest chunk size and records past that limit are missing from the file. CLI: mubit export audit; SDK: client.audit.list(since=..., until=..., kinds=[...]).

The same records appear as a timeline inside the drawers of lessons, proposals, jobs, snapshots, policy and runs, filtered to that object.

Injections

Injection receipts as a list, one row per injection_id: run, step and phase, agent, units (ids on hover), budget used / requested, degraded, killed, empty_reason, policy_version, lesson_set_version, the outcomes that followed (count, mean signal) and the time, joined from the injection.receipt and outcome.record audit records of the selected range. Filters live in the URL; a Pause / Play button controls live polling; each row links to its audit records. Per-unit detail is on the run page.

Empty state: "Injections are recorded each time context is added to a model call."

Compare

Cohort comparison for the selected project and environment: a memory-off cohort, a memory-on cohort and an optional third one, each a set of runs selected with the same filter chips as the Runs page. The page aggregates outcomes per cohort with bootstrap intervals; the grade needs 20 runs with an outcome per cohort. Rows pair runs across cohorts by pair_key (or task) and open a diff of the system text and the outputs of one run per cohort. Cohort definitions can be saved as a view.

Agent pages

Each agent has its own tabs: Overview · Prompts · Skills · Memory · Ask memory · Lessons · Runs · Identity. Runs and Lessons are the project tables with the agent locked; Ask memory (/play) runs a query against the project's instance. Agents register themselves on their first run from mubit.init(agent=...) (default: the script name); create an Agent Card only when you want to manage its prompt and skills from the console. The Overview cards are runs (7d), active lessons, injections (7d) and last outcome: the same four numbers mubit status --agent <id> prints.