IntegrationsAgentsMiniMax Agent

MiniMax Agent Tracing with Litefuse

MiniMax Agent is MiniMax’s desktop coding agent. Its agent daemon — Mavis — runs the coder / general / verifier agents on MiniMax-M2 models and exposes a hook system plus a local SQLite store. This integration combines both: hook events provide triggers and real wall-clock timing, the SQLite store provides per-LLM-call messages, token usage, and cost. The collector is a single Python file with zero dependencies (standard library only) that ships spans straight to Litefuse’s OTLP endpoint — no Mavis source changes, no SDK, no virtualenv.

Each user turn becomes one Litefuse trace: one generation per LLM API call, one tool observation per tool execution, and a full subtree for every subagent delegation.

For AI — automated install

If you’re chatting with an AI agent right now, paste this prompt and the agent will handle the whole install end-to-end:

Read https://litefuse.ai/SKILL.md and follow the instructions to install and configure Litefuse for MiniMax Agent.

The skill will ask for your Litefuse API keys (or walk you through signing up if you don’t have an account yet), then configure everything in place. For step-by-step manual setup instead, continue below.

What gets captured

DataCaptured asNotes
User prompttrace inputfrom the UserPromptSubmit hook
Each LLM API callgeneration observationplan (n tools) #N / response / think #N, named after what the model did; thinking / text / tool_call block structure preserved in the output
Tool executions (input + output)tool observationtool: bash (git) #N — key info in the name, full args in the input; real wall-clock start/end from the hook events
Subagents (task tool)subtreetool (1 subagent) #Nsubagent container → the child’s own plan/tool/response steps, rebuilt from the SQLite store; child usage rolls up into the parent trace. Recursive.
Token usageusage_details on generationAnthropic-style keys (input / output / cache_read_input_tokens / cache_creation_input_tokens), from Mavis’s token_usage table
Costcost_details on generationMavis computes per-call USD cost natively; it is forwarded as-is
Model namemodel on generatione.g. MiniMax-M2.7; provider goes to agent_provider metadata
Tool errorstool observation, level=ERRORwith a status-message preview
Aborted / errored turnsroot span level=WARNING / ERRORin-flight tools close as WARNING (“turn ended before tool completed”)
Session groupingtrace session_idMavis session id (mvs_…); team-plan child sessions group under their parent session
User identitytrace user_id$LITEFUSE_USER_ID, falls back to the OS username
Context watermarktrace metadataagent_context_tokens / agent_context_window from the last message’s usage

Trace structure

A turn that delegates to a subagent produces a trace shaped like this (real example):

Mavis Coder — Turn 16                       (AGENT root span, trace headers)
├── plan (1 tool) #1                        (generation — usage, cost, real latency)
├── tool (1 subagent) #2                    (tool — the delegation as seen by the parent)
│   └── subagent                            (AGENT container — rebuilt from the child session)
│       ├── plan (1 tool) #1                (child-local numbering restarts at #1)
│       ├── tool: glob (hooks) #2
│       ├── plan (2 tools) #3
│       ├── tool: read (litefuse_hook.py) #4
│       ├── tool: read (litefuse_hook.py) #5
│       └── subagent response               (generation — the child's final answer)
├── plan (1 tool) #3
├── tool: bash (ls) #4
└── response                                (generation — the final answer, ends the turn)

Design notes:

  • Hooks trigger, SQLite informs. Mavis hook payloads carry no token usage, no message boundaries, and (mostly) no model name — but everything lands in ~/.mavis/sqlite.db (session_messages + token_usage). The collector records timings live from the hooks and assembles the trace from the database when the turn ends. The dependency is read-only and limited to two tables.
  • One generation per LLM API call, named after what the model didplan (n tools) #N when it requested tools, response for the final text answer, think #N for thinking-only steps — never after which model ran (that’s the model attribute).
  • One step counter per agent container. #N is a single chronological sequence shared by generations and tools, assigned strictly in the database’s message order; each subagent container restarts at #1. A tool’s agent_plan_step metadata points at the agent_step_index of the generation that requested it.
  • Subagent subtrees. The opencode-internal child sessions spawned by the task tool never cross the Mavis hook bridge — instead, when the delegation tool returns, the collector parses the child session id (ses_…) from the result and rebuilds the full three-level subtree from SQLite. The delegation tool span deliberately wraps the container: tool-span duration − container duration = the real overhead of delegating. Nested delegations (a child delegating to a grandchild) recurse.
  • Real timestamps where they exist. Parent tool spans use hook wall-clock times. Child tool timings inside a subtree also use hook times when available (child tool hooks are recorded, just never emitted standalone); otherwise they are estimated from message timestamps and flagged agent_times_estimated.
  • Generation inputs are deltas. The full request payload (system prompt + history) is not observable from Mavis hooks. Each generation’s input is what the model newly received — the user prompt for the first call, the previous step’s tool results after that — flagged agent_input_is_delta.
  • Degraded mode instead of silence. If the SQLite schema ever changes under the collector, traces keep flowing from hook data alone (root + tools + a synthesized response), flagged agent_degraded + agent_degraded_reason so dashboards can spot it.
  • Flat agent_* metadata. All integration fields live at the metadata top level with an agent_ prefix — the same keys as every other Litefuse agent integration, so one dashboard query works across all of them.

When do traces appear?

All spans for a turn upload as one batch when the turn ends (Mavis fires SessionEnd per turn); nothing is visible mid-turn. This is deliberate: Mavis’s tool hooks arrive via an opencode proxy that races far ahead of the daemon’s message persistence, so correct step numbering is only possible once the turn’s messages are settled in SQLite. The same trade-off as the Claude Code integration.

Quick Start

Prerequisites

  • The MiniMax Agent desktop app installed (its data directory ~/.mavis/ exists).
  • Python ≥ 3.8 — any python3 works. Zero third-party dependencies: no SDK, no virtualenv, no pip install.
  • A Litefuse project at https://litefuse.cloud with public + secret keys.

Download the collector script

mkdir -p ~/.mavis/hooks
curl -fsSL https://litefuse.ai/integrations/minimax-agent/litefuse_hook.py \
  -o ~/.mavis/hooks/litefuse_hook.py

The source is also browseable at the same URL — feel free to read it before deploying.

Configure credentials in ~/.mavis/.env

cat > ~/.mavis/.env <<'EOF'
TRACE_TO_LITEFUSE=true
LITEFUSE_PUBLIC_KEY=pk-lf-xxx
LITEFUSE_SECRET_KEY=sk-lf-xxx
LITEFUSE_HOST=https://litefuse.cloud
EOF
chmod 600 ~/.mavis/.env

Register the global hooks

Six tiny hook files route Mavis events to the collector. Global hooks (directly under ~/.mavis/hooks/) apply to all agents — coder, general, verifier, and any team-plan delegations:

for E in SessionStart UserPromptSubmit PreToolUse PostToolUse MessageComplete SessionEnd; do
  L=$(echo "$E" | tr '[:upper:]' '[:lower:]')
  cat > ~/.mavis/hooks/litefuse-$L.md <<EOF
---
hookEvent: $E
type: script
priority: 50
timeout: 30000
---
 
\`\`\`bash
set -a && source ~/.mavis/.env && set +a && python3 ~/.mavis/hooks/litefuse_hook.py $E
\`\`\`
EOF
done

Verify registration (no restart needed — Mavis reloads hook files per event):

~/.mavis/bin/mavis hook list --human | grep litefuse
# Expect six rows, AGENT column "*"

Verify

Send a message in the MiniMax Agent app, wait for the reply to finish, then check the collector log:

MAVIS_LITEFUSE_DEBUG=true  # optional: set in ~/.mavis/.env for verbose logs
tail ~/.mavis/hooks/litefuse_hook.log
# Expected: "SessionEnd mvs_... reason=finished emitted=N"

Open the project in Litefuse — each user message becomes one Mavis <Agent> — Turn N trace with the structure shown above.

Upgrading from v1

The previous version of this hook used the Langfuse Python SDK inside a virtualenv, registered per-agent hooks under ~/.mavis/agents/coder/hooks/, and only emitted one merged “LLM response” per turn with character-estimated tokens. v2 needs none of that:

  1. Back up and remove the old per-agent hook files: mv ~/.mavis/agents/coder/hooks/litefuse-*.md <backup-dir>/leaving them in place would double-fire the hooks once the global ones exist.
  2. Download the new script over ~/.mavis/hooks/litefuse_hook.py (back up first if you’ve customized it).
  3. Register the global hooks as in Quick Start.
  4. Optionally delete the old virtualenv — nothing uses it anymore.

v2 renames observations (plan (n tools) #N / response instead of user message / LLM response), reads real usage and cost from Mavis’s database instead of estimating, and flattens metadata to agent_* keys — update any saved dashboard filters.

Environment variables

Read from ~/.mavis/.env (sourced by the hook files). LITEFUSE_* takes precedence; the equivalent LANGFUSE_* names are accepted as an ecosystem-compatible fallback.

VariableRequiredDescription
LITEFUSE_PUBLIC_KEYYesLitefuse project public key (pk-lf-...).
LITEFUSE_SECRET_KEYYesLitefuse project secret key (sk-lf-...).
LITEFUSE_HOSTNoDefaults to https://litefuse.cloud. Alias: LITEFUSE_BASE_URL.
LITEFUSE_TRACING_ENVIRONMENTNoLitefuse environment for emitted traces. Defaults to production; use development for experiments.
LITEFUSE_USER_IDNoOverrides the trace user_id. Falls back to the OS username.
LITEFUSE_EXTRA_TARGETSNoJSON array of extra targets ([{"host", "public_key", "secret_key", "environment"}]) to double-write traces to (e.g. self-hosted + cloud).
TRACE_TO_LITEFUSENoSet to "false" to switch the collector off without uninstalling. Tracing is on whenever keys are present.
MAVIS_LITEFUSE_DEBUGNoSet to "true" for verbose logging (errors are always logged).
MAVIS_LITEFUSE_MAX_CHARSNoTruncation threshold (in characters) for span inputs/outputs. Default 1000000.
MAVIS_LITEFUSE_DBNoOverride the SQLite path (testing). Default ~/.mavis/sqlite.db.

Metadata reference

All integration fields are flat top-level metadata keys with an agent_ prefix (shared across Litefuse agent integrations). Fields absent from the source are omitted entirely, never padded with null.

Trace root: agent_turn_number, agent_session_id, agent_cwd, agent_model, agent_provider, agent_api_calls, agent_tool_calls, agent_steps, agent_message_count, agent_duration_ms, agent_context_tokens, agent_context_window; agent_parent_session_id + agent_subagent on team-plan child sessions; agent_degraded + agent_degraded_reason in degraded mode.

Generation: agent_turn_number, agent_step_index, agent_provider, agent_stop_reason, agent_tool_call_count, agent_thinking_chars, agent_context_tokens, agent_input_is_delta, truncation markers.

Tool: agent_turn_number, agent_step_index, agent_plan_step (join key: tool.agent_plan_step == generation.agent_step_index), agent_tool_name, agent_tool_call_id, agent_duration_ms (hook-timed) or agent_times_estimated, agent_is_error; agent_subagent_session_id on delegation tools.

Subagent container: agent_subagent: true, agent_session_id (the child ses_… id), plus that run’s agent_api_calls / agent_tool_calls / agent_steps / agent_duration_ms.

How it works

The collector registers six Mavis hooks and keeps per-session state under ~/.mavis/hooks/litefuse_state/:

  1. UserPromptSubmit opens a turn: trace/root ids, turn number (counted from the session’s user messages, so resumed sessions continue numbering), and a high-water mark into session_messages.
  2. PreToolUse / PostToolUse record each tool’s real start/end, args, and result — nothing is sent yet. Tool hooks firing for opencode-internal child sessions (ses_…) are recorded for timing but never emitted standalone (their spans belong to the parent’s subtree).
  3. MessageComplete stores the final answer text.
  4. SessionEnd (fires at each turn end) finalizes: it slices the turn’s assistant messages from SQLite, numbers steps in message order, joins token_usage by message id for usage + cost, expands task delegations into subtrees, and sends everything as OTLP/HTTP JSON to <host>/api/public/otel/v1/traces (Basic auth, 10 s timeout). Trace headers ride on every span.

The collector is fail-open: any unexpected error is logged to ~/.mavis/hooks/litefuse_hook.log and the hook prints a no-op JSON response, so it never blocks or slows Mavis. State files are flock-guarded against concurrent hook firings.

Known limitations

  • Generation inputs are deltas, not the full request payload (agent_input_is_delta) — Mavis hooks don’t expose the provider request. Fixing this (and getting usage into hook payloads) needs upstream support from MiniMax.
  • Subagents can’t nest today. opencode’s subagent sessions don’t get the task tool, so a child can’t delegate to a grandchild — the collector’s recursive subtree support is ready for when that changes.
  • No mid-turn visibility — see When do traces appear?

Troubleshooting

No traces appear in Litefuse. Tail ~/.mavis/hooks/litefuse_hook.log. An empty log means the hooks aren’t firing — check ~/.mavis/bin/mavis hook list --human | grep litefuse shows six rows. A send … failed: line means keys or network: verify the values in ~/.mavis/.env.

A trace has agent_degraded: true. The collector couldn’t read Mavis’s SQLite (path moved, schema changed). The trace structure survives from hook data; usage/cost/thinking are missing. Check the db_… error lines in the log and file an issue.

A trace ends in a WARNING root. That turn was aborted or never produced a final text answer. The WARNING status message says so; it is not a collection error.

Numbers look interleaved oddly in the timeline view. Sort by name, not start time: generations derive start times from neighboring steps, and #N follows the authoritative message order.

Cost shows 0 for some calls. Mavis-provided cost_details are forwarded when present; otherwise Litefuse computes cost from the model name — make sure your Litefuse project has a price entry matching the model (e.g. MiniMax-M2.7) under Settings → Models.

Test the collector manually (uses a development environment so production stays clean):

set -a && source ~/.mavis/.env && set +a
export LITEFUSE_TRACING_ENVIRONMENT=development MAVIS_LITEFUSE_DEBUG=true
H=~/.mavis/hooks/litefuse_hook.py
echo '{"input":{"agentName":"coder","sessionId":"manual-test","prompt":"ping"}}'        | python3 $H UserPromptSubmit
echo '{"input":{"agentName":"coder","sessionId":"manual-test","content":"pong","retryCount":0}}' | python3 $H MessageComplete
echo '{"input":{"agentName":"coder","sessionId":"manual-test","reason":"finished"}}'    | python3 $H SessionEnd
tail ~/.mavis/hooks/litefuse_hook.log
# Expected: "SessionEnd manual-test reason=finished emitted=2"

Resources

Was this page helpful?