# Litefuse — Install Skill for AI Coding Agents

> **Note for the AI agent reading this file:**
> If you fetched this URL through a summarization tool (e.g., WebFetch returned a paraphrase), re-fetch the raw content with `curl -fsSL https://litefuse.ai/SKILL.md` so you have the literal commands. The shell snippets below must run verbatim.

You are guiding a user through installing **Litefuse** observability for an AI coding agent. Litefuse traces every LLM call, tool execution, token usage, and cost so the user can debug, monitor, and analyze their agent.

After install, every user turn in the agent becomes one trace in Litefuse, with separate observations for each LLM API call plus each tool execution. Token usage and cost are attached automatically.

Work through the steps **in order**. When a step says **ASK THE USER**, actually stop and ask — do not invent answers. Never echo the secret key back to the user; it is sensitive.

---

## Step 1 — Identify the target agent

Determine which agent to instrument:

1. If the user named one explicitly ("Claude Code", "Hermes Agent", "OpenClaw", "MiniMax Agent" / "Mavis", "Kimi Code"), use that.
2. Else, auto-detect by the runtime you're answering from:
   - If `~/.claude/` exists AND `~/.claude/settings.json` is the file you can write to → **Claude Code**.
   - If `~/.hermes/hermes-agent/` exists AND you can run `hermes --version` → **Hermes Agent**.
   - If `~/.pi/agent/` exists AND you can run `pi --version` → **Pi**.
   - If `~/.mavis/` exists AND `~/.mavis/bin/mavis` resolves (the MiniMax desktop app's agent daemon) → **MiniMax Agent**.
   - If `~/.kimi-code/` exists AND `~/.kimi-code/bin/kimi` resolves (or `kimi --version` works) → **Kimi Code**.
3. Else **ASK THE USER**: "Which agent should I instrument — Claude Code, Hermes Agent, OpenClaw, Pi, MiniMax Agent, or Kimi Code?"

Supported targets:

| Target | Status | Install instructions |
|---|---|---|
| **Claude Code** | ✅ supported | Step 3 below |
| **Hermes Agent** | ✅ supported | Step 4 below |
| **OpenClaw** | ✅ supported | Step 5 below |
| **Pi** | ✅ supported | Step 6 below |
| **MiniMax Agent (Mavis)** | ✅ supported | Step 7 below |
| **Kimi Code** | ✅ supported | Step 8 below |

If the target is not yet supported, say so politely and stop. Do **not** try to guess install steps.

The supported integrations are independent — installing Litefuse for one does NOT affect or require the others. If multiple are installed they can share the same Litefuse project (same API keys) or use different projects; the user picks.

---

## Step 2 — Get Litefuse credentials (shared across all targets)

You need three values:

- Public key — starts with `pk-lf-`
- Secret key — starts with `sk-lf-`
- Base URL — defaults to `https://litefuse.cloud`. Each agent reads it from a different place: `LITEFUSE_BASE_URL` (Claude Code and Hermes — Hermes also accepts legacy `LANGFUSE_HOST`), the plugin's `baseUrl` config field (OpenClaw), the `baseUrl` field in `~/.pi/agent/litefuse-targets.json` (Pi), `LITEFUSE_HOST` in `~/.mavis/.env` (MiniMax Agent), or `LITEFUSE_BASE_URL` in `~/.kimi-code/litefuse.env` (Kimi Code).

**Reuse-from-other-target shortcut**: before asking, probe each of the other supported agents on this machine for existing Litefuse keys:

```bash
# Probe Claude Code (~/.claude/settings.json)
if [ -f "$HOME/.claude/settings.json" ]; then
  python3 -c "
import json
try:
    e = (json.load(open('$HOME/.claude/settings.json')).get('env') or {})
    pk = e.get('LITEFUSE_PUBLIC_KEY') or e.get('LANGFUSE_PUBLIC_KEY', '')
    sk = e.get('LITEFUSE_SECRET_KEY') or e.get('LANGFUSE_SECRET_KEY', '')
    host = e.get('LITEFUSE_BASE_URL') or e.get('LANGFUSE_BASE_URL', '')
    if pk.startswith('pk-lf-') and sk.startswith('sk-lf-'):
        print(f'FOUND_IN_CLAUDE_CODE pk_prefix={pk[:10]} host={host}')
except Exception: pass
"
fi

# Probe Hermes Agent (~/.hermes/.env) — LITEFUSE_* preferred, legacy LANGFUSE_* fallback
if [ -f "$HOME/.hermes/.env" ]; then
  pk=$(awk -F= '/^(LITEFUSE|LANGFUSE)_PUBLIC_KEY=/ {print $2; exit}' "$HOME/.hermes/.env")
  sk=$(awk -F= '/^(LITEFUSE|LANGFUSE)_SECRET_KEY=/ {print $2; exit}' "$HOME/.hermes/.env")
  host=$(awk -F= '/^(LITEFUSE_(BASE_URL|HOST)|LANGFUSE_(HOST|BASE_URL))=/ {print $2; exit}' "$HOME/.hermes/.env")
  case "$pk:$sk" in
    pk-lf-*:sk-lf-*) echo "FOUND_IN_HERMES pk_prefix=${pk:0:10} host=$host" ;;
  esac
fi

# Probe OpenClaw (~/.openclaw/openclaw.json) — keys live under the plugin entry
if [ -f "$HOME/.openclaw/openclaw.json" ]; then
  python3 -c "
import json
try:
    cfg = json.load(open('$HOME/.openclaw/openclaw.json'))
    entry = ((cfg.get('plugins') or {}).get('entries') or {}).get('openclaw-litefuse-plugin') or {}
    c = entry.get('config') or {}
    pk = c.get('publicKey', '')
    sk = c.get('secretKey', '')
    host = c.get('baseUrl', '')
    if pk.startswith('pk-lf-') and sk.startswith('sk-lf-'):
        print(f'FOUND_IN_OPENCLAW pk_prefix={pk[:10]} host={host}')
except Exception: pass
"
fi
```

```bash
# Probe pi (~/.pi/agent/litefuse-targets.json)
if [ -f "$HOME/.pi/agent/litefuse-targets.json" ]; then
  python3 -c "
import json
try:
    targets = json.load(open('$HOME/.pi/agent/litefuse-targets.json'))
    t = (targets or [{}])[0]
    pk = t.get('publicKey', '')
    sk = t.get('secretKey', '')
    host = t.get('baseUrl', '')
    if pk.startswith('pk-lf-') and sk.startswith('sk-lf-'):
        print(f'FOUND_IN_PI pk_prefix={pk[:10]} host={host}')
except Exception: pass
"
fi
```

```bash
# Probe MiniMax Agent (~/.mavis/.env) — LITEFUSE_* preferred, legacy LANGFUSE_* fallback
if [ -f "$HOME/.mavis/.env" ]; then
  grep -E '^(LITEFUSE|LANGFUSE)_(PUBLIC_KEY|SECRET_KEY|HOST|BASE_URL)=' "$HOME/.mavis/.env" \
    | sed -E 's/(SECRET_KEY=sk-lf-).*/\1<redacted>/' \
    | sed 's/^/FOUND_IN_MAVIS /'
fi
```

```bash
# Probe Kimi Code (~/.kimi-code/litefuse.env) — LITEFUSE_* preferred, legacy LANGFUSE_* fallback
if [ -f "$HOME/.kimi-code/litefuse.env" ]; then
  grep -E '^(LITEFUSE|LANGFUSE)_(PUBLIC_KEY|SECRET_KEY|HOST|BASE_URL)=' "$HOME/.kimi-code/litefuse.env" \
    | sed -E 's/(SECRET_KEY=sk-lf-).*/\1<redacted>/' \
    | sed 's/^/FOUND_IN_KIMI_CODE /'
fi
```

Skip the probe for whichever agent you're currently installing for. If any other agent matched: **ASK THE USER**: "I found existing Litefuse keys configured for `<other-agent>`. Reuse them for `<target>`, or use a different project?" If reuse, skip to **Step 3** / **Step 4** / **Step 5** / **Step 6** / **Step 7** / **Step 8** with those values. Don't display the secret key back — confirm only by `pk_prefix`.

If no existing keys, **ASK THE USER**: "Do you already have Litefuse API keys, or should I help you create new ones?"

### 2a. User already has keys

Ask them to paste:

- The public key
- The secret key
- (Optional) The base URL if they self-host. Default `https://litefuse.cloud`.

**Validate** before continuing:
- `pk-lf-` prefix; total length 44
- `sk-lf-` prefix; total length 44

If validation fails, ask again. Do **not** proceed with malformed keys.

### 2b. User needs to create keys

Give them this walkthrough verbatim:

> 1. Open <https://litefuse.cloud/auth/sign-up> in your browser
> 2. Sign up (Google / GitHub / email)
> 3. Click **+ New project**, pick any name
> 4. **Settings → API Keys → Create new API keys**
> 5. Copy the public key (`pk-lf-…`) and secret key (`sk-lf-…`)
> 6. Paste both back here

If you have browser-control tools available (e.g. `mcp__Claude_in_Chrome__*` for Claude Code, or the `web_extract` tool for Hermes), offer to open the sign-up URL. Otherwise just provide the URL.

Wait for the user to paste keys, then validate as in 2a.

**Important**: store the keys only in the target's config file (`~/.claude/settings.json` for Claude Code, `~/.hermes/.env` for Hermes Agent, `~/.mavis/.env` for MiniMax Agent, `~/.kimi-code/litefuse.env` for Kimi Code). Do not log them, do not echo the secret back to the user in chat, do not write them to any other file.

---

## Step 3 — Claude Code integration

The hook is a single **zero-dependency** Python file (standard library only) — no SDK, no virtualenv, no pip install. Run these sub-steps in order.

### 3a. Verify `python3` exists (≥ 3.8)

Any `python3` works, including macOS's system Python:

```bash
if ! command -v python3 >/dev/null 2>&1 \
    || ! python3 -c "import sys; sys.exit(0 if sys.version_info >= (3,8) else 1)" 2>/dev/null; then
  echo "ERROR: python3 >= 3.8 required; found: $(python3 --version 2>&1 || echo missing)"
fi
```

If the check fails (extremely rare), tell the user to install Python 3 (`brew install python3` / `apt install python3`) and STOP.

### 3b. Detect existing install

```bash
HOOK="$HOME/.claude/hooks/litefuse_hook.py"
if [ -f "$HOOK" ] && grep -q 'otel/v1/traces' "$HOOK" \
    && grep -q 'litefuse_hook.py' "$HOME/.claude/settings.json" 2>/dev/null; then
  echo "ALREADY_INSTALLED_V2"
elif [ -f "$HOOK" ]; then
  # v1 (Langfuse-SDK based, ran inside ~/.claude/hooks/.venv) — upgrade it.
  mv "$HOOK" "$HOOK.bak.v1.$(date +%Y%m%d-%H%M%S)"
  echo "V1_BACKED_UP"
fi

# The old upstream langfuse_hook.py from langfuse.com docs is broken — back
# it up so it can't conflict.
if [ -f "$HOME/.claude/hooks/langfuse_hook.py" ]; then
  mv "$HOME/.claude/hooks/langfuse_hook.py" \
     "$HOME/.claude/hooks/langfuse_hook.py.bak.$(date +%Y%m%d-%H%M%S)"
fi
```

If `ALREADY_INSTALLED_V2` printed, skip to **3e Verify**. Otherwise continue (the settings merge in 3d also handles the v1 → v2 hook-command change).

### 3c. Download the hook script

```bash
mkdir -p "$HOME/.claude/hooks"
curl -fsSL https://litefuse.ai/integrations/claude-code/litefuse_hook.py \
  -o "$HOME/.claude/hooks/litefuse_hook.py"
chmod +x "$HOME/.claude/hooks/litefuse_hook.py"
```

Smoke check:

```bash
python3 -c "
import ast; ast.parse(open('$HOME/.claude/hooks/litefuse_hook.py').read())
print('hook syntax OK')
"
```

### 3d. Merge into `~/.claude/settings.json`

Do **not** overwrite the file blindly — it may hold other env vars and hooks the user cares about. Merge atomically with `jq` (substitute the user's `$PK`, `$SK`, and `$HOST`, where `$HOST` defaults to `https://litefuse.cloud`):

```bash
SETTINGS="$HOME/.claude/settings.json"
mkdir -p "$(dirname "$SETTINGS")"
[ -f "$SETTINGS" ] || echo '{}' > "$SETTINGS"

# Backup before mutating; settings.json holds the user's whole Claude
# Code config, mishaps are recoverable.
cp "$SETTINGS" "$SETTINGS.bak.$(date +%Y%m%d-%H%M%S)"

TMP=$(mktemp)
jq --arg pk "$PK" --arg sk "$SK" --arg host "$HOST" '
  .env = (.env // {}) + {
    "LITEFUSE_PUBLIC_KEY": $pk,
    "LITEFUSE_SECRET_KEY": $sk,
    "LITEFUSE_BASE_URL":   $host
  }
  | .hooks      = (.hooks // {})
  | .hooks.Stop = (.hooks.Stop // [])
  # Drop prior Stop entries referencing the broken upstream langfuse_hook.py
  # OR any older litefuse_hook.py command (v1 used a .venv interpreter).
  | .hooks.Stop |= map(select(
      (.hooks // []) | any((.command // "") |
        (contains("langfuse_hook.py") or contains("litefuse_hook.py"))) | not
    ))
  | .hooks.Stop += [{
      "hooks": [{
        "type": "command",
        "command": "python3 \"$HOME\"/.claude/hooks/litefuse_hook.py"
      }]
    }]
  # v2 has NO SubagentStop hook: subagent subtrees are emitted by the parent
  # session Stop hook. Remove any v1 SubagentStop entry pointing at it.
  | (if .hooks.SubagentStop then
      .hooks.SubagentStop |= map(select(
        (.hooks // []) | any((.command // "") | contains("litefuse_hook.py")) | not
      ))
      | (if (.hooks.SubagentStop | length) == 0
         then .hooks |= del(.SubagentStop) else . end)
     else . end)
' "$SETTINGS" > "$TMP" && mv "$TMP" "$SETTINGS"

# Secret key now lives in this file — tighten permissions.
chmod 600 "$SETTINGS"
```

If `jq` is not installed, ASK the user how to proceed — `brew install jq` / `apt install jq`, or offer to write a one-shot Python script that does the same merge. Do not hand-edit JSON via sed/awk; the file is structured and easy to corrupt.

Upgrade-from-v1 leftovers (only when `V1_BACKED_UP` printed earlier): the old `env.TRACE_TO_LANGFUSE` / `env.LANGFUSE_*` keys are harmless (v2 reads `LITEFUSE_*` first and falls back to `LANGFUSE_*`), and `~/.claude/hooks/.venv` is no longer used — tell the user they may `rm -rf ~/.claude/hooks/.venv` whenever they like. Don't delete it yourself without asking.

### 3e. Verify

Smoke test the hook with the real env vars and empty stdin (fail-open path — the hook should exit 0 with no payload):

```bash
LITEFUSE_PUBLIC_KEY="$PK" \
LITEFUSE_SECRET_KEY="$SK" \
LITEFUSE_BASE_URL="$HOST" \
CLAUDE_CODE_LITEFUSE_DEBUG=true \
python3 "$HOME/.claude/hooks/litefuse_hook.py" < /dev/null
echo "exit=$?"
# Expect: exit=0; ~/.claude/state/litefuse_hook.log will show
# "missing/invalid session_id or transcript_path" — that means config
# parsing and env vars are wired correctly.
```

Then tell the user, verbatim:

> ✅ Litefuse is installed.
>
> Claude Code re-reads `settings.json` on every hook firing, so **no restart is needed**. The Stop hook will fire when I finish this very message — your traces start appearing in Litefuse Cloud right away.
>
> Traces upload at the **end of each turn** (the Stop hook is the only moment the collector runs), one trace per user turn — including a full subtree for any subagent the turn delegated to.
>
> **You can open <https://litefuse.cloud> → your project → Traces now** and watch them flow in as you keep chatting.
>
> Want me to also run an **automated check** on the latest trace? I can pull it via [`litefuse-cli`](https://www.npmjs.com/package/litefuse-cli) (no install required — I'll just use `npx`) and audit it for you: token usage attribution, observation tree, any missing fields. Just reply with **"verify trace"** in your next turn and I'll run it.
>
> Note: on the FIRST Stop hook firing in a long-running session, the hook catches up on the entire transcript and emits one trace per past turn. Use `/clear` first if you want a single clean trace from scratch.

Then skip the other agent integrations (you're done) and go to **Step 9 — Report what changed**.

---


## Step 4 — Hermes Agent integration

Hermes runs as a Python plugin under `~/.hermes/plugins/litefuse/`. It loads in-process inside Hermes' own venv (not a separate one) and subscribes to ten Hermes lifecycle hook events, emitting traces per the Litefuse Agent Trace Spec v1.2 — behaviour-named generations (`plan (n tools) #N` / `response` / `think #N`), real LLM latency, full message I/O including thinking, flat `agent_*` metadata, and subagent subtrees for `delegate_task`. Single code path across CLI / Gateway / TUI / oneshot.

### 4a. Verify Hermes is installed

```bash
hermes --version 2>&1 | head -3
ls -d "$HOME/.hermes/hermes-agent" >/dev/null 2>&1 && echo "HERMES_HOME_OK"
```

If `hermes` is missing or `~/.hermes/hermes-agent/` doesn't exist, tell the user:

> I can't find a Hermes Agent install. Install it first (`pip install hermes-agent` or follow https://hermes-agent.nousresearch.com/docs/getting-started ) and re-run me.

…and STOP.

### 4b. Detect existing install

```bash
PLUGDIR="$HOME/.hermes/plugins/litefuse"
if [ -f "$PLUGDIR/__init__.py" ] && [ -f "$PLUGDIR/plugin.yaml" ]; then
  if grep -qE '^version: *0\.([2-9]|[1-9][0-9])' "$PLUGDIR/plugin.yaml"; then
    echo "ALREADY_INSTALLED_V2"
  else
    # v0.1.x (api:<model> naming, hermes_agent.* metadata) — back up, then upgrade.
    cp -R "$PLUGDIR" "$HOME/.hermes/plugins-backup-$(date +%Y%m%d-%H%M%S)-litefuse"
    echo "V1_BACKED_UP"
  fi
fi
```

If `ALREADY_INSTALLED_V2` printed, skip to **4f Enable** to make sure it's enabled, then **4h Verify**. If `V1_BACKED_UP` printed (note the backup lands *outside* `~/.hermes/plugins/` so Hermes can't load it as a second plugin), continue — the downloads in 4d overwrite in place and existing credentials keep working. Otherwise continue with a fresh install.

### 4c. Install Langfuse SDK v4 into Hermes' venv

The plugin runs in-process inside Hermes' Python interpreter, so the SDK has to live in Hermes' venv (not a separate one):

```bash
"$HOME/.hermes/hermes-agent/venv/bin/pip" install 'langfuse>=4,<5'

# Verify
"$HOME/.hermes/hermes-agent/venv/bin/python3" -c "import langfuse; print('langfuse', langfuse.__version__)"
# Expect: langfuse 4.x.y
```

If install fails (network, build error), surface the error and STOP.

### 4d. Download the plugin files

```bash
mkdir -p "$HOME/.hermes/plugins/litefuse"
curl -fsSL https://litefuse.ai/integrations/hermes-agent/plugin.yaml \
  -o "$HOME/.hermes/plugins/litefuse/plugin.yaml"
curl -fsSL https://litefuse.ai/integrations/hermes-agent/__init__.py \
  -o "$HOME/.hermes/plugins/litefuse/__init__.py"
```

Smoke check:

```bash
"$HOME/.hermes/hermes-agent/venv/bin/python3" -c "
import ast; ast.parse(open('$HOME/.hermes/plugins/litefuse/__init__.py').read())
print('plugin syntax OK')
"
```

### 4e. Add credentials to `~/.hermes/.env`

The plugin reads credentials from Hermes' own `.env` file (Hermes auto-loads it at startup). Append or update; preserve any other keys the user already has:

```bash
ENVFILE="$HOME/.hermes/.env"
touch "$ENVFILE"
cp "$ENVFILE" "$ENVFILE.bak.$(date +%Y%m%d-%H%M%S)"

# Remove any pre-existing Litefuse lines (both LITEFUSE_* and legacy
# LANGFUSE_*), then re-append clean ones. Use a Python script so we
# don't accidentally munge other env entries.
python3 - "$ENVFILE" "$PK" "$SK" "$HOST" <<'PY'
import sys
path, pk, sk, host = sys.argv[1:]
drop = ("LITEFUSE_PUBLIC_KEY=", "LITEFUSE_SECRET_KEY=",
        "LITEFUSE_BASE_URL=", "LITEFUSE_HOST=",
        "LANGFUSE_PUBLIC_KEY=", "LANGFUSE_SECRET_KEY=",
        "LANGFUSE_HOST=", "LANGFUSE_BASE_URL=")
keep_lines = [l for l in open(path).read().splitlines()
              if not l.startswith(drop)]
if keep_lines and keep_lines[-1].strip() != "":
    keep_lines.append("")
keep_lines.extend([
    "# Litefuse observability",
    f"LITEFUSE_PUBLIC_KEY={pk}",
    f"LITEFUSE_SECRET_KEY={sk}",
    f"LITEFUSE_BASE_URL={host}",
])
open(path, "w").write("\n".join(keep_lines) + "\n")
print("env file updated")
PY

chmod 600 "$ENVFILE"
```

### 4f. Enable the plugin

Hermes plugins are opt-in:

```bash
hermes plugins enable litefuse
hermes plugins list | grep -E '^│ *litefuse' || hermes plugins list
# Expect: a row showing `litefuse` with Status = `enabled`
```

### 4g. Restart the gateway if it's running

The gateway daemon caches its plugin manager at startup, so a restart is needed for the new plugin to load. CLI invocations don't need this — they load plugins fresh per process.

```bash
if hermes gateway status 2>/dev/null | grep -q "Gateway service is loaded"; then
  hermes gateway restart
  echo "gateway restarted"
fi
```

### 4h. Verify

Fire a one-shot CLI turn that uses a tool, and check the plugin log:

```bash
hermes -z "use the shell tool to count the lines in ~/.hermes/config.yaml, then tell me the number" 2>&1 | tail -3
sleep 2
tail -3 "$HOME/.hermes/state/litefuse_plugin.log"
# Expect: "litefuse plugin v0.2.0 registered (10 hooks, spec v1.2)"
#       + "Litefuse client ready (host=https://litefuse.cloud env=<default>)"
#       + "turn closed session=YYYYMMDD_HHMMSS_xxxxxx turn=1 steps=3 api=2 tools=1 final=True"
```

If the log doesn't show "turn closed", inspect for errors:

```bash
tail -20 "$HOME/.hermes/state/litefuse_plugin.log"
```

Then tell the user, verbatim:

> ✅ Litefuse plugin installed for Hermes Agent.
>
> Hermes loaded the plugin on the verification turn above — your first trace named `Hermes Agent — Turn 1` is in Litefuse Cloud already.
>
> **You can open <https://litefuse.cloud> → your project → Traces now** to see it.
>
> Coverage notes:
> - **CLI** (`hermes -z`, `hermes chat -q`): ✅ works out of the box.
> - **Gateway** (Feishu / Slack / Discord / Telegram / etc.): ✅ works after the restart in step 4g.
> - **TUI** (`hermes --tui`): ✅ on Hermes ≥ v0.12; older versions don't load plugins in the TUI process — upgrade Hermes if traces are missing there.
> - **Subagents** (`delegate_task`): ✅ each delegation becomes a nested subtree inside the parent turn's trace, with the children's token usage rolled into the trace's total cost.
>
> Want me to also run an **automated check** on the latest trace? I can pull it via [`litefuse-cli`](https://www.npmjs.com/package/litefuse-cli) (no install required — I'll just use `npx` or `bunx`) and audit it for you: token usage attribution, observation tree, any missing fields. Just reply with **"verify trace"** in your next turn and I'll run it.

---

## Step 5 — OpenClaw integration

OpenClaw uses an in-process TypeScript plugin under `~/.openclaw/extensions/` (or any path linked via `openclaw plugins install --link`). It subscribes to the gateway's lifecycle hooks (`message_received`, `llm_input`/`llm_output`, `before_tool_call`/`after_tool_call`, `before_agent_start`/`agent_end`, …) and emits one Litefuse trace per agent turn.

### 5a. Verify OpenClaw is installed and configured for local mode

```bash
command -v openclaw >/dev/null 2>&1 || { echo "ERROR: openclaw CLI not on PATH"; }
openclaw --version 2>&1 | head -1
openclaw config get gateway.mode 2>&1 | tail -1
# Expect: gateway.mode = "local"
```

If `openclaw` is missing, tell the user:

> I can't find an OpenClaw install. Install it first (`npm install -g openclaw` or follow https://docs.openclaw.ai/install ) and re-run me.

…and STOP.

If `gateway.mode` is unset, set it:

```bash
openclaw config set gateway.mode local
```

Also verify Node.js ≥ 22 is on PATH — the plugin's TypeScript build requires it:

```bash
node -v | awk -F. '{ sub("v","",$1); exit ($1<22) }' || echo "ERROR: node ≥ 22 required"
```

### 5b. Detect existing install

```bash
if openclaw plugins inspect openclaw-litefuse-plugin >/dev/null 2>&1; then
  echo "ALREADY_INSTALLED"
fi
```

If `ALREADY_INSTALLED` printed, skip to **5e Configure** (in case credentials need refreshing), then **5g Verify**. Otherwise continue.

### 5c. Clone and build the plugin

The repo ships TypeScript only — `dist/` is gitignored, so build once before linking. Pick any stable location for the source tree; `~/.openclaw/extensions/openclaw-litefuse-plugin` is the convention:

```bash
SRC="$HOME/.openclaw/extensions/openclaw-litefuse-plugin"
if [ ! -d "$SRC/.git" ]; then
  mkdir -p "$(dirname "$SRC")"
  git clone https://github.com/litefuse/openclaw-litefuse-plugin.git "$SRC"
fi
cd "$SRC"
git pull --ff-only 2>/dev/null || true
npm install
npm run build

# Sanity check the build emitted dist/index.js
test -f "$SRC/dist/index.js" || echo "ERROR: build did not produce dist/index.js"
```

If `npm install` or `npm run build` fails (network, toolchain), surface the error to the user and STOP — don't try to work around it.

### 5d. Link the plugin into OpenClaw

```bash
openclaw plugins install --link "$SRC" 2>&1 | tail -3
openclaw plugins inspect openclaw-litefuse-plugin 2>&1 | head -10
# Expect: Status: loaded
#         Source: <SRC>/dist/index.js
```

The first `--link` install may also emit a `[Litefuse] Missing required configuration` warning — that's expected before 5e completes; ignore it.

### 5e. Configure credentials in `~/.openclaw/openclaw.json`

OpenClaw's `config set` performs a structured atomic merge, so it won't clobber other entries. Set the three required values (substitute the user's `$PK`, `$SK`, and `$HOST`, where `$HOST` defaults to `https://litefuse.cloud`):

```bash
openclaw config set plugins.entries.openclaw-litefuse-plugin.config.publicKey "$PK"
openclaw config set plugins.entries.openclaw-litefuse-plugin.config.secretKey "$SK"
openclaw config set plugins.entries.openclaw-litefuse-plugin.config.baseUrl   "$HOST"
openclaw config set plugins.entries.openclaw-litefuse-plugin.config.environment "production"

# Tighten permissions — openclaw.json now holds the secret key.
chmod 600 "$HOME/.openclaw/openclaw.json"
```

The secret key now lives in `~/.openclaw/openclaw.json` only. Do **not** echo it back to the user; confirm only by `pk_prefix`.

### 5f. Restart the gateway

The gateway caches plugin config at startup, so a restart is required for the new credentials to apply:

```bash
if openclaw gateway restart 2>&1 | grep -q "not loaded"; then
  # Service hasn't been installed as a launchd/systemd unit yet.
  # Start it in the foreground (user can later run `openclaw gateway install`).
  pkill -f "openclaw gateway" 2>/dev/null; sleep 1
  nohup openclaw gateway >/tmp/openclaw-gateway.log 2>&1 &
  sleep 5
fi

# Confirm it's responding.
openclaw health 2>&1 | head -3
```

### 5g. Verify

`openclaw plugins doctor` re-instantiates each plugin in isolation and surfaces activation logs — the fastest sanity check before running a real turn:

```bash
openclaw plugins doctor 2>&1 | grep Litefuse
# Expect three lines:
#   [Litefuse] Target added: <host>
#   [Litefuse] Plugin initialized with 1 target(s)
#   [Litefuse] Plugin activated (baseUrl: <host>)
```

If those three lines don't appear, inspect the gateway log:

```bash
LOGFILE=$(ls -t /tmp/openclaw/openclaw-*.log 2>/dev/null | head -1)
[ -n "$LOGFILE" ] && grep -E '"message":"\[Litefuse\]' "$LOGFILE" | tail -5
```

Then fire a real agent turn so the plugin can flush a complete trace. Use whichever model provider the user already has authed (`openclaw infer model auth status | jq '.auth.providers[]'`); fall back to asking them. For example, if Anthropic is configured:

```bash
SID="litefuse-verify-$(date +%s)"
openclaw agent --model "anthropic/claude-haiku-4-5" \
  --session-id "$SID" \
  -m "say 'hello' and nothing else" 2>&1 | tail -10
```

Confirm the trace landed via the Litefuse API (auth happens via the same pk/sk):

```bash
sleep 2
curl -sS -u "$PK:$SK" \
  "$HOST/api/public/traces?limit=1&tags=openclaw" \
  | python3 -c "import sys,json; d=json.load(sys.stdin)['data']; print(d[0]['id'], d[0]['name'], d[0]['timestamp']) if d else print('NO_TRACE_YET')"
# Expect: <traceId> openclaw-<8hex> <ISO timestamp>
```

If the curl returns `NO_TRACE_YET`, look for `[Litefuse] Flushed 1 target(s)` in the gateway log — that confirms the SDK attempted to send. The most common cause of `Flushed` never appearing is the LLM call failing before `agent_end` fires; check `lane task error` in the same log.

Then tell the user, verbatim:

> ✅ Litefuse plugin installed for OpenClaw.
>
> Your first trace `openclaw-<8hex>` is in Litefuse Cloud already.
>
> **You can open <https://litefuse.cloud> → your project → Traces now** to see it.
>
> Coverage notes:
> - **`openclaw agent` turns** (`--local` or via the gateway): ✅ each user message becomes one trace named `openclaw-<8hex>` with nested generation + tool observations.
> - **TUI / `openclaw chat`**: ✅ same plugin code path; sessions are grouped by `sessionId`.
> - **Channels** (Telegram, Slack, Discord, …): ✅ once the gateway is up; channel id is captured under `openclaw.channel.id` metadata.
> - **Multi-target**: set `plugins.entries.openclaw-litefuse-plugin.config.targets` to an array if you need to fan traces out to multiple Litefuse projects.
>
> Want me to also run an **automated check** on the latest trace? I can pull it via [`litefuse-cli`](https://www.npmjs.com/package/litefuse-cli) (no install required — I'll just use `npx`) and audit it for you: token usage attribution, observation tree, any missing fields. Just reply with **"verify trace"** in your next turn and I'll run it.

---

## Step 6 — Pi integration

[Pi](https://pi.dev) loads TypeScript extensions in-process from `~/.pi/agent/extensions/` — the Litefuse extension is a single file with zero npm dependencies and no build step.

### 6a. Verify Pi is installed

```bash
pi --version
# Any recent version works; extension auto-discovery is built in.
```

If `pi` is missing, stop and tell the user to install it first (`npm install -g @earendil-works/pi-coding-agent`).

### 6b. Idempotency check

```bash
ls ~/.pi/agent/extensions/litefuse/index.ts 2>/dev/null && echo "EXISTING_INSTALL"
```

If it exists this is an upgrade: back it up first, then re-download (next step) — config in `litefuse-targets.json` is untouched.

```bash
cp ~/.pi/agent/extensions/litefuse/index.ts \
   ~/.pi/agent/extensions/litefuse/index.ts.bak.$(date +%Y%m%d%H%M%S) 2>/dev/null || true
```

### 6c. Download the extension

No dependency install needed — the file uses only Node built-ins and Pi loads TypeScript directly.

```bash
mkdir -p ~/.pi/agent/extensions/litefuse
curl -fsSL https://litefuse.ai/integrations/pi/index.ts \
  -o ~/.pi/agent/extensions/litefuse/index.ts
```

### 6d. Configure credentials

Write `~/.pi/agent/litefuse-targets.json` (no shell-profile edits). Use python so an existing multi-target file is merged, not clobbered:

```bash
python3 - "$LF_PK" "$LF_SK" "$LF_HOST" <<'PY'
import json, os, sys
pk, sk, host = sys.argv[1], sys.argv[2], sys.argv[3] or "https://litefuse.cloud"
path = os.path.expanduser("~/.pi/agent/litefuse-targets.json")
try:
    targets = json.load(open(path))
    assert isinstance(targets, list)
except Exception:
    targets = []
entry = {"publicKey": pk, "secretKey": sk, "baseUrl": host, "environment": "production"}
targets = [t for t in targets if t.get("publicKey") != pk] + [entry]
# Newest entry first: the extension treats the env-configured target as primary,
# then file targets in order.
targets.insert(0, targets.pop())
json.dump(targets, open(path, "w"), indent=2)
os.chmod(path, 0o600)
print(f"wrote {path} ({len(targets)} target(s))")
PY
```

(Substitute `$LF_PK` / `$LF_SK` / `$LF_HOST` with the values from Step 2.)

### 6e. Verify

```bash
PI_LITEFUSE_DEBUG=true pi --no-session -p "Reply with exactly: ok"
tail -3 ~/.pi/agent/litefuse.log
# Expect: "extension loaded, 1 target(s): https://litefuse.cloud"
#       + "turn complete session=... turn=1 api_calls=1 tool_calls=0"
```

Note: this runs one real LLM call through the user's configured provider. If Pi has no provider configured, skip the live test and just confirm the `extension loaded` line appears.

**If you are the Pi session being instrumented**: your current session loaded extensions at startup and won't trace until reloaded. Tell the user to run `/reload` in Pi (or start a new session) — the next turn will then appear in Litefuse.

---

## Step 7 — MiniMax Agent (Mavis) integration

The MiniMax desktop app's agent daemon is called **Mavis**; its data directory is `~/.mavis/`. The integration is a zero-dependency Python collector triggered by six Mavis hooks, reading usage/cost from Mavis's own SQLite store. Docs: https://litefuse.ai/integrations/agents/minimax-agent

### 7a. Verify Mavis is installed

```bash
ls "$HOME/.mavis/bin/mavis" && command -v python3 && python3 --version
# Expect: the symlink exists (it points into the MiniMax app bundle) and Python >= 3.8
```

If `~/.mavis/` doesn't exist, the MiniMax Agent desktop app isn't installed — stop and tell the user.

### 7b. Idempotency check

```bash
ls ~/.mavis/hooks/litefuse_hook.py 2>/dev/null && head -5 ~/.mavis/hooks/litefuse_hook.py
ls ~/.mavis/hooks/litefuse-*.md 2>/dev/null
ls ~/.mavis/agents/*/hooks/litefuse-*.md 2>/dev/null
```

- v2 already installed (script header says "v2" AND six global `~/.mavis/hooks/litefuse-*.md` exist): skip to **7e** to update credentials only.
- **v1 detected** (hook files under `~/.mavis/agents/<name>/hooks/` instead of global, or the script imports `langfuse`): back up, then remove the per-agent hook files — leaving them would double-fire once the global ones exist:

```bash
B=~/.mavis/hooks/litefuse-v1-backup-$(date +%Y%m%d)
mkdir -p "$B" && cp ~/.mavis/hooks/litefuse_hook.py "$B/" 2>/dev/null
mv ~/.mavis/agents/*/hooks/litefuse-*.md "$B/" 2>/dev/null
echo "v1 backed up to $B"
```

### 7c. Download the collector script

```bash
mkdir -p ~/.mavis/hooks
curl -fsSL https://litefuse.ai/integrations/minimax-agent/litefuse_hook.py \
  -o ~/.mavis/hooks/litefuse_hook.py
python3 -c "import ast; ast.parse(open('$HOME/.mavis/hooks/litefuse_hook.py').read()); print('syntax OK')"
```

### 7d. Register the six global hooks

Global hooks (directly under `~/.mavis/hooks/`) apply to all agents — coder, general, verifier, and team-plan delegations. No daemon restart needed; Mavis re-reads hook files per event.

```bash
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
~/.mavis/bin/mavis hook list --human | grep litefuse
# Expect: six rows, AGENT column "*"
```

### 7e. Configure credentials in `~/.mavis/.env`

```bash
# Remove any pre-existing Litefuse lines (both LITEFUSE_* and legacy LANGFUSE_*),
# then re-append clean ones — preserve unrelated entries.
touch ~/.mavis/.env && cp ~/.mavis/.env ~/.mavis/.env.bak.$(date +%s)
grep -vE '^(TRACE_TO_LITEFUSE|LITEFUSE_|LANGFUSE_)' ~/.mavis/.env > ~/.mavis/.env.tmp || true
cat >> ~/.mavis/.env.tmp <<EOF
TRACE_TO_LITEFUSE=true
LITEFUSE_PUBLIC_KEY=<PUBLIC_KEY>
LITEFUSE_SECRET_KEY=<SECRET_KEY>
LITEFUSE_HOST=<BASE_URL>
EOF
mv ~/.mavis/.env.tmp ~/.mavis/.env
# Secret key now lives in this file — tighten permissions.
chmod 600 ~/.mavis/.env
```

### 7f. Verify

```bash
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":"skill-install-test","prompt":"ping"}}'        | python3 $H UserPromptSubmit
echo '{"input":{"agentName":"coder","sessionId":"skill-install-test","content":"pong","retryCount":0}}' | python3 $H MessageComplete
echo '{"input":{"agentName":"coder","sessionId":"skill-install-test","reason":"finished"}}'    | python3 $H SessionEnd
tail -3 ~/.mavis/hooks/litefuse_hook.log
# Expect: "sent 2 span(s) -> <host> HTTP 200" + "SessionEnd skill-install-test reason=finished emitted=2"
# (This synthetic turn has no SQLite rows, so the trace is flagged agent_degraded — expected for the test;
#  real turns driven by the MiniMax app will not be.)
rm -f ~/.mavis/hooks/litefuse_state/skill-install-test.*
```

Real verification: the user sends a message in the MiniMax Agent app; each turn appears in Litefuse as `Mavis <Agent> — Turn N`.

---

## Step 8 — Kimi Code integration

Kimi Code (Moonshot AI's `kimi` CLI) has no hook system; the integration is a zero-dependency Python **polling collector** that parses each session's `wire.jsonl` event log and runs every 30 s via `launchd` (macOS) or every minute via `cron` (Linux). Docs: https://litefuse.ai/integrations/agents/kimi-code

### 8a. Verify Kimi Code is installed (and python3 ≥ 3.8)

```bash
ls -d "$HOME/.kimi-code" >/dev/null 2>&1 && echo "KIMI_CODE_HOME_OK"
("$HOME/.kimi-code/bin/kimi" --version 2>/dev/null || kimi --version 2>/dev/null) | head -1
command -v python3 && python3 -c "import sys; sys.exit(0 if sys.version_info >= (3,8) else 1)" && echo "PYTHON_OK"
```

If `~/.kimi-code/` doesn't exist, Kimi Code isn't installed — stop and tell the user.

### 8b. Idempotency check

```bash
HOOK="$HOME/.kimi-code/hooks/litefuse_hook.py"
if [ -f "$HOOK" ] && grep -q 'otel/v1/traces' "$HOOK"; then
  echo "ALREADY_INSTALLED_V2"
elif [ -f "$HOOK" ] || grep -lq 'from langfuse import' "$HOME"/.kimi*/litefuse_hook.py 2>/dev/null; then
  # v1 (Langfuse-SDK based) — back it up.
  [ -f "$HOOK" ] && mv "$HOOK" "$HOOK.bak.v1.$(date +%Y%m%d-%H%M%S)" && echo "V1_BACKED_UP"
fi
```

If `ALREADY_INSTALLED_V2` printed, skip to **8d** to update credentials only, then **8f Verify**.

### 8c. Download the collector script

```bash
mkdir -p "$HOME/.kimi-code/hooks"
curl -fsSL https://litefuse.ai/integrations/kimi-code/litefuse_hook.py \
  -o "$HOME/.kimi-code/hooks/litefuse_hook.py"
chmod +x "$HOME/.kimi-code/hooks/litefuse_hook.py"
python3 -c "import ast; ast.parse(open('$HOME/.kimi-code/hooks/litefuse_hook.py').read()); print('syntax OK')"
```

### 8d. Configure credentials in `~/.kimi-code/litefuse.env`

The collector runs under `launchd`/`cron` with an empty environment, so credentials live in this env file (read on every run — no service restart needed after edits):

```bash
ENVFILE="$HOME/.kimi-code/litefuse.env"
touch "$ENVFILE" && cp "$ENVFILE" "$ENVFILE.bak.$(date +%s)"
grep -vE '^(TRACE_TO_LITEFUSE|LITEFUSE_|LANGFUSE_)' "$ENVFILE" > "$ENVFILE.tmp" || true
cat >> "$ENVFILE.tmp" <<EOF
TRACE_TO_LITEFUSE=true
LITEFUSE_PUBLIC_KEY=<PUBLIC_KEY>
LITEFUSE_SECRET_KEY=<SECRET_KEY>
LITEFUSE_BASE_URL=<BASE_URL>
EOF
mv "$ENVFILE.tmp" "$ENVFILE"
chmod 600 "$ENVFILE"
```

### 8e. Schedule the collector

macOS (launchd, every 30 s; plists don't expand `$HOME`, the heredoc below expands it at write time):

```bash
cat > ~/Library/LaunchAgents/com.kimi.litefuse.plist <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key><string>com.kimi.litefuse</string>
    <key>ProgramArguments</key>
    <array>
        <string>/usr/bin/env</string>
        <string>python3</string>
        <string>$HOME/.kimi-code/hooks/litefuse_hook.py</string>
    </array>
    <key>StartInterval</key><integer>30</integer>
    <key>RunAtLoad</key><true/>
</dict>
</plist>
EOF
launchctl bootout gui/$(id -u)/com.kimi.litefuse 2>/dev/null
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.kimi.litefuse.plist
launchctl print gui/$(id -u)/com.kimi.litefuse | head -3
```

Linux (cron, every minute):

```bash
(crontab -l 2>/dev/null | grep -v 'kimi/hooks/litefuse_hook.py'; \
 echo "* * * * * python3 \$HOME/.kimi-code/hooks/litefuse_hook.py") | crontab -
crontab -l | grep litefuse
```

### 8f. Verify

Run the collector once by hand with debug logging:

```bash
KIMI_LITEFUSE_DEBUG=true python3 "$HOME/.kimi-code/hooks/litefuse_hook.py"
tail -5 "$HOME/.kimi-code/state/litefuse_hook.log"
# With existing Kimi sessions, expect: "Emitted N turn(s) in X.XXs -> <host>"
# With no sessions yet, the log stays quiet — that's fine.
```

Then tell the user, verbatim:

> ✅ Litefuse is installed for Kimi Code.
>
> The collector polls every 30 seconds and uploads each turn **after it finishes** — send a message in Kimi Code, wait for the answer plus ~30 s, and the trace `Kimi Code — Turn N` appears in Litefuse Cloud.
>
> Note: on its first run the collector caught up on your existing sessions, so past turns may already be there.
>
> **You can open <https://litefuse.cloud> → your project → Traces now.**
>
> Want me to also run an **automated check** on the latest trace? I can pull it via [`litefuse-cli`](https://www.npmjs.com/package/litefuse-cli) (no install required — I'll just use `npx`) and audit it for you: token usage attribution, observation tree, any missing fields. Just reply with **"verify trace"** in your next turn and I'll run it.

---

## Step 9 — Report what changed

Summarize for the user (omit the secret key from the summary).

For **Claude Code**:
- **Python interpreter**: `$(command -v python3)` (`$(python3 --version)`) — the hook is zero-dependency (standard library only); no virtualenv, no SDK install.
- **Hook script**: `~/.claude/hooks/litefuse_hook.py` (downloaded from https://litefuse.ai; sends OTLP directly to the Litefuse endpoint). If a v1 hook was present, its backup path.
- **settings.json**: merged in `env.LITEFUSE_PUBLIC_KEY`, `env.LITEFUSE_SECRET_KEY` (redacted), `env.LITEFUSE_BASE_URL`, and a Stop hook entry (`python3 …/litefuse_hook.py`). Any v1 Stop/SubagentStop entries pointing at the hook were replaced/removed. Existing unrelated entries were preserved.
- If upgrading from v1: `~/.claude/hooks/.venv` is no longer used and can be deleted by the user.

End with: "Your secret key now lives in `~/.claude/settings.json`. Keep that file readable only by you (`chmod 600`)."

For **Hermes Agent**:
- **Plugin**: `~/.hermes/plugins/litefuse/{plugin.yaml, __init__.py}` v0.2.0 (downloaded from https://litefuse.ai; implements Litefuse Agent Trace Spec v1.2). If a v0.1.x install was present, its backup path (`~/.hermes/plugins-backup-<timestamp>-litefuse`).
- **Langfuse SDK**: installed into Hermes' own venv (`~/.hermes/hermes-agent/venv/`)
- **`~/.hermes/.env`**: appended `LITEFUSE_PUBLIC_KEY`, `LITEFUSE_SECRET_KEY` (redacted), `LITEFUSE_BASE_URL`; any legacy `LANGFUSE_*` Litefuse lines replaced. Existing unrelated entries preserved; backup at `~/.hermes/.env.bak.<timestamp>`.
- **`~/.hermes/config.yaml`**: `plugins.enabled` updated by `hermes plugins enable litefuse` to include `litefuse`.
- **Gateway**: restarted if it was running.

End with: "Your secret key now lives in `~/.hermes/.env`. Keep that file readable only by you (`chmod 600`)."

For **OpenClaw**:
- **Plugin source**: `~/.openclaw/extensions/openclaw-litefuse-plugin` (cloned from https://github.com/litefuse/openclaw-litefuse-plugin)
- **Build output**: `~/.openclaw/extensions/openclaw-litefuse-plugin/dist/index.js` (produced by `npm run build`)
- **Plugin linked into OpenClaw**: registered as `openclaw-litefuse-plugin` via `openclaw plugins install --link`. Confirm with `openclaw plugins inspect openclaw-litefuse-plugin`.
- **`~/.openclaw/openclaw.json`**: merged in `plugins.entries.openclaw-litefuse-plugin.config.{publicKey, secretKey (redacted), baseUrl, environment}`. Existing entries preserved by `openclaw config set`.
- **`gateway.mode`**: ensured to be `local` (set if missing).
- **Gateway**: restarted to pick up the new config.

End with: "Your secret key now lives in `~/.openclaw/openclaw.json`. Keep that file readable only by you (`chmod 600`)."

For **Pi**:
- **Extension**: `~/.pi/agent/extensions/litefuse/index.ts` (downloaded from https://litefuse.ai; zero npm dependencies, no build step). Previous version (if any) backed up alongside as `index.ts.bak.<timestamp>`.
- **`~/.pi/agent/litefuse-targets.json`**: merged in `{publicKey, secretKey (redacted), baseUrl, environment}`; existing targets preserved; `chmod 600` applied.
- **Reload**: an already-running interactive Pi session needs `/reload` (or a new session) to pick the extension up.

End with: "Your secret key now lives in `~/.pi/agent/litefuse-targets.json`. Keep that file readable only by you (`chmod 600`)."

For **MiniMax Agent (Mavis)**:
- **Python interpreter**: `$(command -v python3)` (`$(python3 --version)`) — the collector is zero-dependency (standard library only); no virtualenv, no SDK install.
- **Collector script**: `~/.mavis/hooks/litefuse_hook.py` (downloaded from https://litefuse.ai; hooks trigger it, Mavis's SQLite supplies usage/cost, spans go straight to the Litefuse OTLP endpoint). If a v1 install was present, its backup path (`~/.mavis/hooks/litefuse-v1-backup-<date>/`).
- **Global hooks**: six files `~/.mavis/hooks/litefuse-{sessionstart,userpromptsubmit,pretooluse,posttooluse,messagecomplete,sessionend}.md` — registered for ALL agents (AGENT column `*` in `mavis hook list`). Any v1 per-agent copies under `~/.mavis/agents/<name>/hooks/` were moved to the backup (leaving them would double-fire).
- **`~/.mavis/.env`**: appended `TRACE_TO_LITEFUSE=true`, `LITEFUSE_PUBLIC_KEY`, `LITEFUSE_SECRET_KEY` (redacted), `LITEFUSE_HOST`; pre-existing Litefuse lines replaced, unrelated entries preserved; backup at `~/.mavis/.env.bak.<timestamp>`.
- **No restart needed** — Mavis re-reads hook files per event.

End with: "Your secret key now lives in `~/.mavis/.env`. Keep that file readable only by you (`chmod 600`)."

For **Kimi Code**:
- **Python interpreter**: `$(command -v python3)` (`$(python3 --version)`) — the collector is zero-dependency (standard library only); no virtualenv, no SDK install.
- **Collector script**: `~/.kimi-code/hooks/litefuse_hook.py` (downloaded from https://litefuse.ai; parses each session's `wire.jsonl` and sends OTLP directly to the Litefuse endpoint). If a v1 hook was present, its backup path.
- **Scheduler**: `~/Library/LaunchAgents/com.kimi.litefuse.plist` (`launchd`, every 30 s) on macOS, or a per-minute crontab line on Linux.
- **`~/.kimi-code/litefuse.env`**: wrote `TRACE_TO_LITEFUSE=true`, `LITEFUSE_PUBLIC_KEY`, `LITEFUSE_SECRET_KEY` (redacted), `LITEFUSE_BASE_URL`; pre-existing Litefuse lines replaced, unrelated entries preserved; backup at `~/.kimi-code/litefuse.env.bak.<timestamp>`.
- **State / log**: `~/.kimi-code/state/litefuse_state.json` (per-session offsets) and `~/.kimi-code/state/litefuse_hook.log`.

End with: "Your secret key now lives in `~/.kimi-code/litefuse.env`. Keep that file readable only by you (`chmod 600`)."

---

## Step 10 — Follow-up verification (when the user comes back next turn)

By the time the user asks "verify trace" / "check litefuse" / "did it work", one or more traces have been emitted. Run via `npx` (or `bunx` / `pnpm dlx` if they prefer) — no global install needed. The npm package is `litefuse-cli` and the binary it ships is `litefuse`:

```bash
# Use npx -y to skip the "install package?" prompt; the package gets cached
# after the first run. Substitute `bunx litefuse-cli` or `pnpm dlx litefuse-cli`
# if those are present and preferred.
LITEFUSE="npx -y litefuse-cli"

# Pull the most recent trace
LATEST=$($LITEFUSE api traces list --limit 1 | jq -r '.data[0].id')
echo "Latest trace: $LATEST"
$LITEFUSE api traces get "$LATEST" > /tmp/litefuse_verify.json
```

Run an audit on `/tmp/litefuse_verify.json`. The exact shape depends on which integration the user installed:

**Claude Code traces:**
- Trace **name** starts with `Claude Code - Turn`
- Top-level `sessionId`, `userId`, `release`, `tags` populated
- Observation breakdown: 1 SPAN root + 1 EVENT `user message` + N generations + M tools, **ending with `Final response (#N)`** generation. If the trace ends with something other than a `Final response` (e.g., a `Decision to call tool` or `Tool call`), the turn was emitted prematurely and the hook is on an older version — re-download from `https://litefuse.ai/integrations/claude-code/litefuse_hook.py`.
- Every GENERATION has a `model`
- Every GENERATION with `metadata.claude_code.is_last_in_message == true` has a non-empty `usageDetails`
- Every TOOL has `metadata.claude_code.uuid` and `metadata.claude_code.parentUuid`
- `metadata.claude_code.tool_use_id` cross-references the corresponding generation's tool_use block
- **Metadata is sparse**: fields absent from the source JSONL row are NOT padded with `null` — e.g., a normal assistant row's metadata won't have `claude_code.apiErrorStatus`, `claude_code.error`, `claude_code.stop_details` keys at all. Only fields that genuinely appeared in JSONL are present.

**Hermes Agent traces** (plugin v0.2.0, spec v1.2 — same shape as Pi):
- Trace **name** matches `Hermes Agent — Turn N`
- Top-level `sessionId` (format `YYYYMMDD_HHMMSS_<6or8hex>`), `userId`, `tags` populated; tags include `hermes-agent` and one `model:<name>` entry
- Trace `input` = the user prompt (plain text); trace `output` = the final answer text
- Observation breakdown: 1 AGENT root + N generations (`plan (n tools) #N` / `think #N`, ending with `response`) + M tools (`tool: <name> (<info>) #N`) — all flat under the root
- Generations and tools share ONE step-number sequence (`#N` = `agent_step_index`)
- Every GENERATION has a `model` and non-empty `usageDetails` (Anthropic-style keys: `input`, `output`, optional cache keys), plus real latency (startTime ≠ endTime)
- The `response` generation's output contains the final answer; if the model emitted reasoning, generation outputs carry a `reasoning` field and metadata has `agent_thinking_chars`
- Every TOOL's metadata has `agent_tool_name`, `agent_tool_call_id`, `agent_step_index`, and `agent_plan_step` — and `agent_plan_step` equals the `agent_step_index` of the plan generation that requested it
- **Metadata is flat with the `agent_` prefix** (e.g. `agent_duration_ms`, `agent_stop_reason`) and sparse — no `null` padding, no `hermes_agent.*` nesting (that's v0.1.x; re-download if seen)
- If the turn delegated to subagents: a `tool (n subagents) #N` TOOL span containing a nested `subagent` AGENT container with the child's own `plan/tool/subagent response` steps renumbered from #1; child generations carry usage that rolls into the trace's `totalCost`

**OpenClaw traces:**
- Trace **name** matches `openclaw-<8hex>` (first 8 chars of the traceId)
- Top-level `tags` includes `openclaw`; `environment` matches the configured value
- Top-level `sessionId` is the OpenClaw `sessionId` (set after `session_start` / `agent_end`); `userId` is the message sender, optionally prefixed by the configured `userId` (e.g. `alice/<sender>`)
- Observation breakdown: 1 root SPAN `enter_openclaw_system` + 1 nested SPAN `invoke_agent <agentId>` + N GENERATION `chat <model>` + M TOOL `execute_tool <name>` + optional session/gateway EVENTs
- Every `chat <model>` GENERATION has a non-empty `model` and a `usageDetails` block. For Anthropic models, expect `input`, `output`, `cache_read_input_tokens`, and `cache_creation_input_tokens` keys
- Every `execute_tool <name>` is observation-type `TOOL` (not a plain span) — required for Litefuse's graph view
- Every observation's `metadata` carries `openclaw.run.id`, `openclaw.turn.id`, `openclaw.channel.id`, and `openclaw.version`
- The agent span's `metadata` includes `agent.duration_ms`, `agent.message_count`, `agent.success`; on failure it also has `agent.error`

**Pi traces:**
- Trace **name** matches `Pi Agent — Turn N`
- Top-level `sessionId` (Pi session UUID), `userId`, `tags` populated; tags include `pi-agent` and one `model:<name>` entry
- Observation breakdown: 1 AGENT root + N generations (`plan (n tools) #N` / `think #N`, ending with `response`) + M tools (`tool: <name> (<info>) #N`)
- Every GENERATION has a `model` and non-empty `usageDetails` (Anthropic-style keys: `input`, `output`, optional cache keys)
- Every TOOL's metadata has `agent_tool_name`, `agent_tool_call_id`, `agent_step_index`, and `agent_plan_step` — and `agent_plan_step` equals the `agent_step_index` of the plan generation that requested it
- **Metadata is flat with the `agent_` prefix** (e.g. `agent_duration_ms`, `agent_context_usage`) and sparse — no `null` padding
- If the turn delegated to subagents: a `tool (n subagents) #N` TOOL span containing a nested `subagent` AGENT container with the child's own `plan/tool/subagent response` steps; child generations carry usage that rolls into the trace's `totalCost`

**MiniMax Agent (Mavis) traces:**
- Trace **name** matches `Mavis <Agent> — Turn N` (e.g. `Mavis Coder — Turn 3`)
- Top-level `sessionId` (Mavis session id, `mvs_<32hex>`), `userId`, `tags` populated; tags include `mavis-<agent>` (e.g. `mavis-coder`) and one `model:<name>` entry
- Trace `input` = the user prompt; trace `output` = the final answer text
- Observation breakdown: 1 AGENT root + N generations (`plan (n tools) #N` / `think #N`, ending with `response`) + M tools (`tool: <name> (<info>) #N`) — all flat under the root
- Generations and tools share ONE step-number sequence (`#N` = `agent_step_index`), assigned in message order
- Every GENERATION has a `model` (e.g. `MiniMax-M2.7`) and non-empty `usageDetails` (Anthropic-style keys); most also carry `costDetails.total` forwarded from Mavis
- Every TOOL's metadata has `agent_tool_name`, `agent_tool_call_id`, `agent_step_index`, and `agent_plan_step` — and `agent_plan_step` equals the `agent_step_index` of the plan generation that requested it; hook-timed tools carry `agent_duration_ms`, estimated ones carry `agent_times_estimated`
- **Metadata is flat with the `agent_` prefix** and sparse — no `null` padding, no `mavis.*` nesting (that's v1; re-download if seen)
- If the turn delegated via the `task` tool: a `tool (1 subagent) #N` TOOL span containing a nested `subagent` AGENT container (metadata `agent_subagent: true`, `agent_session_id: ses_…`) with the child's own `plan/tool/subagent response` steps renumbered from #1; child generations carry usage that rolls into the trace's `totalCost`. Nested delegations recurse.
- A trace with `agent_degraded: true` means the collector couldn't read Mavis's SQLite — structure survives but usage/cost/thinking are missing; check `~/.mavis/hooks/litefuse_hook.log` for `db_…` errors

**Kimi Code traces:**
- Trace **name** matches `Kimi Code — Turn N`
- Top-level `sessionId` (Kimi session id, `session_<uuid>`), `userId`, `tags` populated; tags include `kimi-code` and one `model:<name>` entry (e.g. `model:kimi-code/kimi-for-coding`)
- Trace `input` = the user prompt; trace `output` = the final answer text
- Observation breakdown: 1 AGENT root + N generations (`plan (n tools) #N` / `think #N`, ending with `response`) + M tools (`tool: <name> (<info>) #N`) — all flat under the root except subagent subtrees
- Generations and tools share ONE step-number sequence (`#N` = `agent_step_index`)
- Every completed GENERATION has a `model` and non-empty `usageDetails` (Anthropic-style keys: `input`, `output`, cache keys), real latency, and `agent_time_to_first_token_ms` in metadata
- Generation metadata carries `agent_input_scope: "turn"` — inputs are reconstructed from the current turn only (wire.jsonl has no full request payloads); this is expected, not a defect
- Every TOOL's metadata has `agent_tool_name`, `agent_tool_call_id`, `agent_step_index`, and `agent_plan_step` — and `agent_plan_step` equals the `agent_step_index` of the plan generation that requested it
- **Metadata is flat with the `agent_` prefix** and sparse — no `null` padding, no nesting (nested `usage` metadata is v1; re-download if seen)
- If the turn delegated via the `Agent` tool: a `tool (1 subagent) #N` TOOL span containing a nested `subagent` AGENT container (metadata `agent_subagent: true`, `agent_subagent_id: agent-<n>`, `agent_subagent_status`) with the child's own `plan/tool/subagent response` steps renumbered from #1; child generations carry usage that rolls into the trace's `totalCost`. Kimi subagents don't get the `Agent` tool themselves, so trees deeper than two agent levels can't occur — that's a Kimi limitation, not a collection bug
- A root with `level=WARNING` and "interrupted"/"cancelled" status is the faithful record of a turn that never completed (killed process, expired `/login`, `turn.cancel`) — not a collection error

If any check fails, surface the specific observation id + the failure. For Claude Code, ask the user to share `~/.claude/state/litefuse_hook.log`. For Hermes Agent, ask for `~/.hermes/state/litefuse_plugin.log`. For OpenClaw, ask for the gateway log lines matching `[Litefuse]` from `/tmp/openclaw/openclaw-$(date +%Y-%m-%d).log`. For Pi, ask for `~/.pi/agent/litefuse.log` (re-run with `PI_LITEFUSE_DEBUG=true` for verbose lines). For MiniMax Agent, ask for `~/.mavis/hooks/litefuse_hook.log` (set `MAVIS_LITEFUSE_DEBUG=true` in `~/.mavis/.env` for verbose lines). For Kimi Code, ask for `~/.kimi-code/state/litefuse_hook.log` (set `KIMI_LITEFUSE_DEBUG=true` in `~/.kimi-code/litefuse.env` for verbose lines). If `totalCost` is 0, explain that's because their Litefuse project doesn't have a price config for the model — point them at **Settings → Models** in the Litefuse UI.

---

## For Litefuse maintainers — extending this SKILL

Every integration here implements one shared contract — the **Litefuse agent-trace spec**:

```bash
curl -fsSL https://litefuse.ai/litefuse-agent-trace-spec.md
```

It is the normative reference for what a good trace looks like: one trace per user turn, generation/tool naming (`plan (n tools) #N` / `response`, `tool: <name> (<info>) #N`), Anthropic-style `usage_details` keys, the subagent three-layer subtree, flat `agent_*` metadata, and send-once timing. **Read it before adding or modifying an integration** so the new collector matches the others; when the spec and an older integration disagree, the spec wins.

To add support for a new agent:

1. **Step 1**: change the agent's row in the support table from ⏳ to ✅ and point it at the new section.
2. **Step 2 is shared** — the credential flow doesn't need duplication. Extend the reuse-from-other-target probe block to also check the new agent's config file, so credentials carry across all installed targets.
3. **Append a new "Step N: <Agent> integration"** mirroring Step 3 (Claude Code), Step 4 (Hermes), Step 5 (OpenClaw), or Step 6 (Pi) substep structure:
   - `Na. Detect runtime` (e.g., binary on PATH, venv, config dir)
   - `Nb. Idempotency check`
   - `Nc. Install dependencies` (whatever the agent needs)
   - `Nd. Download / link the hook or plugin` (from `https://litefuse.ai/integrations/<agent>/<filename>` for hosted scripts, or from the agent-specific GitHub repo for full plugins)
   - `Ne. Configure` (the agent's config file — merge, don't clobber)
   - `Nf. Restart / enable` (if the agent has plugin opt-in or daemon semantics)
   - `Ng. Verify`
4. **Renumber** the existing report + follow-up steps to keep them last.
5. **Step "Report what changed"**: add a per-agent block enumerating what files were touched and where the secret key now lives.
6. **Step "Follow-up verification"**: add a per-agent block listing the trace name pattern, expected observation tree, and required metadata keys for `litefuse-cli` auditing.
7. If a section grows beyond ~80 lines, split into a sub-skill at `https://litefuse.ai/integrations/<agent>/SKILL.md` and have Step 1 instruct the AI agent to fetch that URL.
8. Keep host scripts alongside the docs: `public/integrations/<agent>/<filename>` → served at `https://litefuse.ai/integrations/<agent>/<filename>`. Full plugins that need a build step (e.g. OpenClaw's TypeScript plugin) live in their own GitHub repo; reference the repo URL directly in the install step.

The dispatch pattern (Step 1 → identify → branch) is intentionally simple: one file, single fetch, predictable for the AI agent.
