Self-optimizing prompts
A prompt baked into your deploy artifact can only improve on redeploy. This pattern moves the system prompt (and tool/skill definitions) into Mubit as versioned server-side resources: agents fetch the active version at call time, outcomes accrue against it, and the optimizer proposes better candidates from that evidence — reviewed, activated, and rolled back without touching agent code. Use it when your agents already record outcomes and prompt tuning has become a recurring manual chore.
How it works
set_prompt(v1) ──► agents get_prompt() per call ──► outcomes accrue
│
activate ◄── review diff ◄── optimize_prompt() ◄─────┘
│
└─► (regression? → rollback to the retired version)Every set_prompt creates a new PromptVersion (active, candidate, retired, or archived); activation is atomic and the displaced version stays addressable for rollback. optimize_prompt reads the agent's accumulated lessons and outcomes — the same signal the learning loop runs on — and produces a candidate version plus an optimization_summary and confidence. It never activates by default; you diff, then promote. Skills get the identical lifecycle with optimize_skill and the skill-version calls.
Serving the active prompt
The helper layer resolves the agent from the ambient session context and returns the content string directly:
The 60-second prompt cache is Python-only at 0.11.0 — the JS getPrompt() helper fetches on every call, so add your own memoization on hot paths. Rust has no session-context helper layer; fetch with the payload-style call directly on the client: client.get_prompt(json!({ "agent_id": "ari" })) and read version.content.
Because agents fetch the prompt per call (cache notwithstanding), an activation propagates to the whole fleet within seconds — no redeploy.
Version lifecycle calls
optimize_prompt is top-level in all three SDKs (a typed helper from SDK v0.12.0 in Python/JS — client.optimize_prompt(agent_id=..., auto_activate=False, project_id=...) / client.optimizePrompt({...}) — and typed in Rust since 0.11); the surrounding version management lives on the raw surface:
| Call | Python | JS | Rust |
|---|---|---|---|
| Propose a candidate | client.optimize_prompt(agent_id=..., project_id=...) | client.optimizePrompt({...}) | client.optimize_prompt(OptimizePromptOptions::new("ari")) — typed |
| Get active / specific version | client.advanced.get_prompt | client.advanced.getPrompt | client.get_prompt(json!({...})) |
| List versions | client.advanced.list_prompt_versions | client.advanced.listPromptVersions | client.list_prompt_versions(json!({...})) |
| Diff two versions | client.advanced.get_prompt_diff | client.advanced.getPromptDiff | client.get_prompt_diff(json!({...})) |
| Activate a version | client.advanced.activate_prompt_version | client.advanced.activatePromptVersion | client.activate_prompt_version(json!({...})) |
The propose row is typed everywhere from SDK v0.12.0 (optimize_prompt / optimizePrompt / Rust's OptimizePromptOptions, which predates it); on 0.11.0 the Python/JS calls are wire pass-throughs with the same call shape. The get/list/diff/activate rows remain wire pass-throughs on every version — pass the wire field names (agent_id, version_id, version_a_id/version_b_id, project_id). In Python and JS the version-management ops live under client.advanced.*; in Rust the same long-tail ops sit payload-style directly on Client.
The compact loop, from Nimbus's nightly cron:
resp = client.optimize_prompt(agent_id="ari", project_id=PROJECT, auto_activate=False)
if resp["confidence"] >= 0.6:
active = client.advanced.get_prompt(agent_id="ari")
diff = client.advanced.get_prompt_diff(
agent_id="ari",
version_a_id=active["version"]["version_id"],
version_b_id=resp["candidate"]["version_id"],
)
post_for_review(resp["optimization_summary"], diff["diff_text"])
# a human approves → client.advanced.activate_prompt_version(
# agent_id="ari", version_id=resp["candidate"]["version_id"])The candidate's material is the rationales and directive_hints from your recorded outcomes — the better your attribution hygiene, the better the candidates. The full runnable workflow — including the console equivalents, shadow testing, and rollback — is Prompt optimization.
Champion/challenger, hands-off
Instances can run the comparison for you. With prompt shadow serving enabled, while a candidate exists get_prompt alternates deterministically between the champion and the challenger on a time bucket, so the challenger accrues real outcome statistics with no client-side changes. The champion/challenger evaluator then surfaces the winner — and when auto-promotion is enabled, an activated challenger is watched against the displaced champion's baseline score and automatically rolled back if post-promotion outcomes regress. Treat this as an instance feature to opt into once manual review has built trust in the loop, not as a starting point.
When not to use it
- Externally change-controlled prompts — if the prompt text is reviewed, signed off, and audited outside your systems, it must not drift by optimization. Pin it in your artifact (or keep versions in Mubit but never grant
optimize_prompta path to activation). - Too little signal — the optimizer can synthesize a confident-looking candidate from a handful of outcomes. Wait for a meaningful sample with some genuine failures before acting on candidates.
- Prompts that encode hard invariants — safety-critical constraints belong in
ruleentries or code-level checks (Guardrails from failures), not in text an optimizer is allowed to rewrite.
Related
- Prompt optimization — the full runnable lifecycle, with console equivalents
- The outcome attribution loop — the signal the optimizer feeds on
- Reflection to lessons — the lessons folded into candidates
- Projects, agents, and skills — the resource model behind prompt and skill versions
- Control HTTP reference — the prompt lifecycle endpoints on the wire
Configuration. Shadow serving and its bucket width are instance configuration (MUBIT_CL_PROMPT_SHADOW, MUBIT_CL_PROMPT_SHADOW_BUCKET_SECS), as is the confidence threshold auto_activate must clear server-side. Defaults keep activation deliberate: shadow serving off, candidates surface for review.