---
name: polardbx-zero-pxfuse
description: Provision a PolarDB-X PXFuse project (LLM observability) and install the pxfuse0 CLI to automatically capture agent sessions, turns, and tool calls. Use when the user asks for LLM tracing/observability, wants to debug prompt or token cost, or asks to install/configure PXFuse.
compatibility: Requires Node.js >= 22 for the pxfuse0 CLI path. The alternative langfuse SDK path requires Node.js >= 20 or Python >= 3.9 (langfuse Python SDK v4 requires Python >= 3.10). Network access to zero.polardbx.com (provisioning) and to the returned project host (trace ingestion).
metadata:
  version: 0.2.3
  homepage: https://zero.polardbx.com/
---

# PolarDB-X Zero — PXFuse (LLM observability)

PXFuse is a PolarDB-X hosted LLM observability platform. The recommended path is: provision a project, then install the `pxfuse0` CLI and bind it to yourself (the coding agent). After that, your sessions, turns, and tool calls are **automatically captured** by hook scripts — no manual trace calls needed. An alternative path using the langfuse SDK directly is available for instrumenting your own application code. Projects are temporary (15 days) and per-caller.

## 1. Provision a project

**POST** `https://zero.polardbx.com/api/v1/pxfuse/projects`

Content-Type: `application/json`, body `{}` (optional `clientIp` = your public IP for the instance whitelist).

```sh
curl -fsSL -X POST 'https://zero.polardbx.com/api/v1/pxfuse/projects' \
  -H 'Content-Type: application/json' -d '{}'
```

Response:

```json
{
  "project": {
    "assignmentId": "pxfa_xxx",
    "projectId": "cmxxxxxxxxxx",
    "projectName": "zero-a1b2c3",
    "publicKey": "pk-lf-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "secretKey": "sk-lf-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "host": "http://pxc-xxx-pxf.polardbx.rds.aliyuncs.com:3000",
    "expiresAt": "2027-01-15T00:00:00.000Z",
    "account": {
      "email": "pxfa_xxx@pxfuse.zero.local",
      "password": "<plaintext, returned only here>",
      "loginUrl": "http://pxc-xxx-pxf.polardbx.rds.aliyuncs.com:3000/auth/sign-in"
    }
  },
  "token": "pxz_xxx"
}
```

- `host` + `publicKey` + `secretKey` are the three values the CLI (and the SDK) needs. The plain `secretKey` is shown **once** at creation — store it now. Keep the `token` and pass it back via the `X-PXZ-Token` header (see §6): repeating the POST with it returns your existing project instead of creating a new one.
- `account` is **optional** — it is present only when the instance provisions through self-registration. When present, it is a real console login for this project; the `password` is returned in this response and nowhere else. Hand it to the user verbatim (do not print it into shared logs) and tell them to change it after first sign-in. When `account` is absent, skip anything that talks about signing in.
- If you lost the secret key but still have the token, fetch it again with `GET /api/v1/pxfuse/projects/<assignmentId>/credentials` and header `X-PXZ-Token: <token>`.
- The project expires at `expiresAt` (15 days) and is then purged.
- If `host` resolves to a private address (10.x / 172.16-31.x / 192.168.x), that instance is reachable only inside the VPC. Ingestion from a laptop will time out — tell the user instead of retrying silently.

## 2. Install pxfuse0 and bind it to yourself

The pxfuse0 CLI follows an **install once → auto-capture everything** model. Once installed and bound to your agent host, hook scripts automatically report your sessions, turns, and tool calls to PXFuse. You do **not** need to send traces manually.

### One-line install (recommended)

Pass the secret key via the `PXFUSE0_SECRET_KEY` environment variable so it never appears in the process list:

```sh
curl -fsSL 'https://zero.polardbx.com/pxfuse/install' | \
  PXFUSE0_SECRET_KEY='<SECRET_KEY>' bash -s -- \
    --agent <agent> --host <HOST> --public-key <PUBLIC_KEY>
```

Replace `<agent>` with the host you are running as:

| You are | `--agent` value |
|---|---|
| Claude Code | `claude` |
| OpenAI Codex | `codex` |
| Qoder | `qoder` |
| QoderWork | `qoderwork` |
| Hermes | `hermes` |
| Qwen Code | `qwen` |

### Manual install (if the one-liner is blocked)

```sh
# 1. Install the CLI globally (Node.js >= 22 required)
npm install -g @polardbx/pxfuse0@latest

# 2. Configure connection (secret via stdin, never in argv)
printf '%s' "<SECRET_KEY>" | pxfuse0 setup \
  --host <HOST> --public-key <PUBLIC_KEY> --secret-key-stdin

# 3. Bind to your agent host
pxfuse0 attach <agent>

# 4. Verify
pxfuse0 status
pxfuse0 doctor   # additionally checks API connectivity, not just local install
```

**Security**: the secret key is stored in `~/.pxfuse0/config.json` with mode 0600. It is never echoed in full — `pxfuse0 config show` / `pxfuse0 status` mask it, showing only the last 4 characters (e.g. `****abcd`). Never echo the secret key in logs or source code.

## 3. What happens next (auto-capture)

After `pxfuse0 attach <agent>`, a hook script + skill are registered in your agent's configuration. The hook fires on session lifecycle events and uploads to `<HOST>/api/public/ingestion` using your project credentials. Every host registers its session-end event (`Stop` for most, `on_session_finalize` for hermes); most also register session start, prompt submission, and tool-call events. Capture works in one of two modes:

- **Event-driven** (claude, codex, hermes, qwen, qoderwork): each lifecycle event is converted to an ingestion event and uploaded as it happens. The trace appears and grows while the session is still running — claude, qwen, and hermes emit the trace at session start; codex and qoderwork at the first prompt.
- **Transcript replay** (qoder only): the hook fires at session end, reads the session transcript plus the local token store, reconstructs the turns, LLM generations and tool calls since the last run, and batch-uploads them. A qoder session shows up once it stops, not while it is still running.

**Do not attempt to manually send traces or call the ingestion API yourself.** The hooks handle everything.

To confirm the hooks are active:

```sh
pxfuse0 status
pxfuse0 doctor   # verifies hook registration + API connectivity
```

Or use the API self-check in §4.

## 4. View & query your data

### Dashboard (if account was returned)

If the provisioning response included an `account` object, open `account.loginUrl` and sign in with `account.email` / `account.password`. That account owns exactly this one project, so the trace list is correct on first load. Traces appear within seconds — event-driven hosts stream them while the session runs; qoder uploads once the session stops.

If there is no `account` in the response, the caller has no dashboard login — use the API below instead and say so plainly rather than sending the user to a sign-in page they cannot pass.

### API (programmatic access)

Verify credentials and connectivity:

```sh
curl -fsS -u "<PUBLIC_KEY>:<SECRET_KEY>" "<HOST>/api/public/projects"
# -> {"data":[{"id":"cm...","name":"zero-xxxxxx","organization":{...}}]}
```

Read recent traces:

```sh
curl -fsS -u "<PUBLIC_KEY>:<SECRET_KEY>" "<HOST>/api/public/traces?limit=5"
```

Some self-hosted builds back `/api/public/traces` with MySQL and return `500` (reserved-word error on the `release` column) — that failure says nothing about ingestion, so fall back to `GET /api/public/projects` rather than concluding traces were lost.

**CLI query commands**: the pxfuse0 CLI can also read your data directly — it calls the same Langfuse public API under the hood, so no dashboard login is required:

```sh
pxfuse0 traces --limit 5      # recent traces
pxfuse0 trace <traceId>       # single trace detail
pxfuse0 stats --daily         # daily usage & cost
pxfuse0 scores                # score list
```

Any of these accepts `--json` for machine-readable output. The dashboard and the raw public API (above) remain the two other ways to view the same data.

## 5. Alternative: instrument your own app with the langfuse SDK

If you want to add observability to **your own application code** (as opposed to capturing this agent's activity), use the official Langfuse SDK directly. PXFuse is wire-compatible with Langfuse, so the standard SDK works out of the box.

### Install the SDK

```sh
npm install langfuse            # Node.js >= 20 (SDK 3.x, current)
# or
pip install langfuse            # Python >= 3.9; v4.x needs Python >= 3.10
```

Check which Python SDK version you got before picking an example below:
`pip show langfuse` — on Python 3.9 you get `3.7.0` (the last 3.x release);
Python >= 3.10 gets `4.x`. Both 3.x and 4.x use the same (new) API shown below.
Note: `python -c "import langfuse; print(langfuse.__version__)"` only works on
4.x — 3.x does not export `__version__` and raises `AttributeError`.

### Configure credentials

Write the three values as environment variables (never inline them in source, never echo the secret key — show `[REDACTED]`).

**The three host spellings differ across SDKs** — the two languages read different
variable names, and langfuse-python v4 renamed its own. Set all three and no
SDK generation can go wrong:

```sh
export LANGFUSE_BASEURL='<HOST>'              # Node.js SDK reads this one
export LANGFUSE_HOST='<HOST>'                 # langfuse-python 3.x reads this one
export LANGFUSE_BASE_URL='<HOST>'             # langfuse-python v4 prefers this one (LANGFUSE_HOST still works, deprecated)
export LANGFUSE_PUBLIC_KEY='<PUBLIC_KEY>'     # pk-lf-...
export LANGFUSE_SECRET_KEY='<SECRET_KEY>'     # sk-lf-...
```

Setting only `LANGFUSE_HOST` is the single most common way to break the Node.js path: the JS SDK
ignores it, silently falls back to `https://cloud.langfuse.com`, and your keys are rejected there
with `401`. If you cannot set all three, pass the host explicitly instead:
`new Langfuse({ baseUrl: '<HOST>' })`.

For a project checked into git, put them in `.env` and make sure `.env` is git-ignored.

### Send the first trace

**Pick the example matching your language, not your SDK minor version.** The
Python SDK changed its API in **v3.0** (the OTel rewrite): `end()` no longer
accepts `output`/`usage` (it only takes `end_time`), those arguments moved to
`update()`, the `usage` argument was renamed `usage_details`, and
`trace.generation(...)` gave way to `langfuse.start_observation(...)`. That
classic syntax is 2.x-era — it fails on **every** langfuse-python >= 3.0 with
`unexpected keyword argument 'output'`, not just on v4. All 3.x and 4.x releases
share the new API, so the single Python example below covers both. The Node.js
SDK (3.x, current) still uses the classic API — the two SDKs broke at different
major versions, so never copy examples across languages.

Node.js (SDK 3.x — current):

```js
import { Langfuse } from 'langfuse';

const langfuse = new Langfuse();          // host comes from LANGFUSE_BASEURL, not LANGFUSE_HOST

const trace = langfuse.trace({ name: 'hello-pxfuse', userId: 'demo-user' });
const gen = trace.generation({
  name: 'chat-completion',
  model: 'qwen-plus',
  input: [{ role: 'user', content: 'ping' }],
});
gen.end({ output: 'pong', usage: { input: 1, output: 1 } });

await langfuse.flushAsync();              // required before the process exits
```

Python (SDK 3.x and 4.x — new API, langfuse-python >= 3.0):

```python
from langfuse import Langfuse

langfuse = Langfuse()                     # reads LANGFUSE_* from env

obs = langfuse.start_observation(         # 3.0+: replaces trace.generation(...)
    name="hello-pxfuse",
    as_type="generation",
    model="qwen-plus",
    input=[{"role": "user", "content": "ping"}],
)
obs.update(output="pong", usage_details={"input": 1, "output": 1})
obs.end()                                 # 3.0+: end() only accepts end_time

langfuse.flush()                          # required before the process exits
```

### Verify the connection

`flushAsync()` does **not** throw when ingestion is rejected — the SDK only logs the error and
resolves normally. So "no exception" is not evidence that anything arrived.

The one check that is both cheap and reliable is `GET /api/public/projects` — it validates the host
and the key pair together:

```sh
curl -fsS -u "$LANGFUSE_PUBLIC_KEY:$LANGFUSE_SECRET_KEY" "$LANGFUSE_BASEURL/api/public/projects"
```

In Python the SDK wraps the same call: `langfuse.auth_check()`. Treat a non-200 as failure and
report it — do not retry silently.

To read the trace back, `GET /api/public/traces?limit=5` works on managed PXFuse instances.
`flush` / `flushAsync` is mandatory in short-lived scripts — the SDK batches in the background
and unflushed traces are lost on exit.

## 6. Manage / release

### Local hook management

```sh
# Remove the PXFuse hook + skill from an agent (local only — does not delete the server project)
pxfuse0 detach <agent>

# Check hook status for all agents
pxfuse0 status
```

**Important**: `pxfuse0 detach` only removes the local hook + skill binding. It does **not** release the server-side project. To release the project and free the slot, use the DELETE API below.

### Server-side project management

```sh
# list your projects
curl -fsSL 'https://zero.polardbx.com/api/v1/pxfuse/projects' \
  -H 'X-PXZ-Token: <token>'

# re-read credentials (plaintext secret key)
curl -fsSL 'https://zero.polardbx.com/api/v1/pxfuse/projects/<assignmentId>/credentials' \
  -H 'X-PXZ-Token: <token>'

# release; the next POST then provisions a brand-new project
curl -fsSL -X DELETE 'https://zero.polardbx.com/api/v1/pxfuse/projects/<assignmentId>' \
  -H 'X-PXZ-Token: <token>'
```

Projects auto-expire 15 days after provisioning. There is no renewal — provision a fresh one instead. One caller holds **one** project at a time; POSTing again with the same token returns the same project (`alreadyExisted: true`).

## 7. Errors

| Symptom | Meaning |
|---|---|
| `node: command not found` | Node.js is not installed — install Node.js >= 22 from https://nodejs.org/ |
| `pxfuse0: command not found` | the global npm install did not add pxfuse0 to PATH — check `npm prefix -g` and ensure its `bin/` is in your `$PATH` |
| `pxfuse0 attach` succeeds but no traces appear in dashboard | run `pxfuse0 doctor` first (checks hook registration + API connectivity); if that passes, self-check with `curl -u "<PUBLIC_KEY>:<SECRET_KEY>" "<HOST>/api/public/projects"` |
| `401` from the Node SDK while curl with the same keys works | only `LANGFUSE_HOST` was set — the JS SDK needs `LANGFUSE_BASEURL` and went to `cloud.langfuse.com` |
| `TypeError: ... unexpected keyword argument 'output'` (Python) | langfuse-python >= 3.0 is installed but the code uses the 2.x-era classic API — `end()` only accepts `end_time`; pass `output`/`usage_details` to `update()` instead (see the Python example in §5) |
| `401` / `invalid api key` from the SDK or API | wrong or revoked key pair — re-read credentials or re-provision |
| `500` from `fetchTraces` / `GET /api/public/traces` | MySQL-backed self-hosted builds only (reserved word `release`); ingestion is unaffected — verify with `GET /api/public/projects` instead |
| `503` no capacity | every PXFuse instance is full. Whether a replacement is on the way depends on server-side config — retrying once or twice is fine, but if it keeps failing, report it to the user instead of retrying in a loop |
| `429` | rate limited — slow down |
| ingestion times out, `host` is a private IP | the instance is VPC-only; run from inside the VPC or ask the user for a tunnel |
| traces never appear (SDK path) | `flush` / `flushAsync` was not awaited before exit |