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

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

.env
MUBIT_API_KEY="mbt_<instance>_<key_id>_<secret>"
MUBIT_ENDPOINT="https://api.mubit.ai"

Full implementation

import os
from mubit import Client
 
RUN_ID = "support:acme:ticket-42"
 
client = Client(
    api_key=os.environ["MUBIT_API_KEY"],
    endpoint=os.getenv("MUBIT_ENDPOINT", "https://api.mubit.ai"),
    transport="http",
)
 
client.agents.register(agent_id="support", role="support", run_id=RUN_ID)
client.agents.register(agent_id="billing", role="billing", run_id=RUN_ID)
 
client.memory.remember(
    "Customer Taylor wants concise Friday updates and is asking about invoice INV-7781.",
    kind="fact",
    metadata={"customer": "taylor", "ticket": 42},
    run_id=RUN_ID,
    agent_id="support",
)
client.memory.remember(
    "Invoice INV-7781 has a stale tax table mismatch on line item 4.",
    kind="trace",
    metadata={"invoice": "INV-7781", "source": "billing-review"},
    run_id=RUN_ID,
    agent_id="billing",
)
 
context = client.memory.context(
    "Draft the next customer-safe update with the billing root cause and next step.",
    lane="legacy",
    mode="full",
    max_token_budget=800,
    run_id=RUN_ID,
)
 
checkpoint = client.checkpoint(
    session_id=RUN_ID,
    agent_id="support",
    label="pre-response-compaction",
    context_snapshot=context.get("context_block", ""),
)
 
handoff = client.agents.handoffs.create(
    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.agents.handoffs.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.lessons.reflect(run_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.outcomes.record(
        run_id=RUN_ID,
        agent_id="support",
        reference_id=lesson_id,
        good=True,
        score=0.75,
        rationale="The support response used the stored customer preference and the validated billing root cause.",
    )
 
strategies = client.lessons.strategies(
    run_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", [])),
})
ℹ️Note

A freshly reflected lesson may enter long-term memory as a pending candidate (the validation gate is on by default, accept threshold 0.6) and is not immediately surfaced until enough evidence accrues. The outcomes you record against it are exactly how it accrues that evidence and gets promoted to active — the control stream emits context.lesson_validation_passed / context.lesson_validation_failed alongside context.lesson_promoted. Operators can disable the gate with MUBIT_CONTROL_LESSON_VALIDATION_ENABLED=0.

ℹ️Note

The answer was grounded on more than the lesson alone, so attribute the win to every recalled entry that informed it. client.outcomes.record accepts unit_ids (a list of the recalled entry ids — e.g. the customer fact and the billing trace) so each one's reinforcement counters update, not just the lesson reference_id (which is never double-counted). The Rust RecordOutcomeOptions field is entry_ids.

Operational notes

  • Start with the Client namespaces and drop to client.raw.invoke(op, payload) only for operations that have no namespace method yet.
  • mubit.init(instrument=True) or mubit.wrap_openai(...) feeds Python LLM traces into this same memory loop automatically; see LLM providers.

Next steps