> ## Documentation Index
> Fetch the complete documentation index at: https://docs.memanto.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Langfuse

> Turn Langfuse observability signal — errors, failed evals, latency and cost anomalies — into durable Memanto memories, live from your app or on a one-shot sync.

# Langfuse + Memanto

<img src="https://unpkg.com/@lobehub/icons-static-svg@latest/icons/langfuse-color.svg" alt="Langfuse" width="120" style={{marginBottom: "1.5rem"}} />

Langfuse records **what went wrong**. Memanto remembers **the lesson**.

This integration connects them: failing spans, failed evaluations, and latency or cost anomalies become durable memories your agents can recall — instead of the same mistake being re-learned on every run.

<CardGroup cols={2}>
  <Card title="One memory per signature" icon="layer-group">
    A thousand identical failures become **one** memory whose confidence reflects how often it happened — not a thousand near-duplicates.
  </Card>

  <Card title="Two ways in" icon="arrows-split-up-and-left">
    A live SDK handler for instant capture from your app, and a CLI sync that reads the Langfuse API. They share a ledger, so running both is safe.
  </Card>

  <Card title="Idempotent" icon="rotate">
    Re-running never duplicates. A recurring failure **updates** its memory in place; an unchanged one is skipped.
  </Card>

  <Card title="Your rules" icon="sliders">
    Score names, value ranges, and what counts as slow or expensive are all project-specific — so you decide, and nothing is assumed on your behalf.
  </Card>
</CardGroup>

## The core idea

Memanto performs no deduplication on write, so piping raw traces in would drown recall — one bad deploy could write thousands of near-identical memories.

Instead, observations are grouped by **error signature**: the operation name plus a message with its volatile parts (ids, numbers, emails, IPs, paths, quoted strings) normalized away.

```
Langfuse observations (thousands)
   │  filter    level=ERROR, your score rules, your latency/cost budgets
   │  group     signature = operation + normalized message
   │  reconcile new → write · changed → update in place · same → skip
   ▼
Memanto (dozens)
```

A real example — **812 observations collapsing to 2 memories**:

```
Namespace <str> not found in generate-response          error   confidence 0.60
Slow: generate-response (anthropic.claude-sonnet-4-6)   observation
```

<Note>
  Everything is **rule-based** — no LLM calls, no token cost. Grouping is a regex-normalized hash; confidence is `min(0.95, 0.60 + 0.15 × log₁₀(occurrences))`, so 1 occurrence scores 0.60 and 100 scores 0.90.
</Note>

## Which path do you want?

|              | Live SDK handler                          | CLI sync                              |
| ------------ | ----------------------------------------- | ------------------------------------- |
| **Setup**    | `pip install langfuse-memanto` + one line | Already in `memanto`                  |
| **Latency**  | Seconds                                   | When you run it                       |
| **Requires** | `langfuse>=3` in your app                 | Nothing — reads the Langfuse API      |
| **Captures** | Errors, latency                           | Everything, including scores and cost |

Most teams run **both**: the handler for instant error capture, and a periodic sync for the signals that only exist server-side.

***

## Path 1 — CLI sync

No app changes. Works with **any** Langfuse version, including v2.

<Steps>
  <Step title="Look at your project first">
    You cannot choose a latency budget or a score rule without seeing your own data.

    ```bash theme={null}
    memanto migrate langfuse --discover
    ```

    ```
     Name         Type     Count  Observed    Suggested rule
     rating       NUMERIC  60     1.0 … 5.0   --score-fail 'rating<3.4'
     user-thumbs  BOOLEAN  3      0.0 … 1.0   --score-fail 'user-thumbs=false'

     Operation          Count  p50 ms  p95 ms  p99 ms  cost p95
     generate-response  300    2000    2000    40000   $0.002409
    ```

    It reads your **actual** range and refuses to guess direction — Langfuse documents no convention for whether a higher score is better.
  </Step>

  <Step title="Save what you want captured">
    ```bash theme={null}
    memanto migrate langfuse \
      --capture errors,slow \
      --latency-percentile 95 \
      --save
    ```

    Stored per Langfuse project in `~/.memanto/migrate/langfuse/config.json`.
  </Step>

  <Step title="Preview, then sync">
    ```bash theme={null}
    memanto migrate langfuse --dry-run
    memanto migrate langfuse
    ```

    Run it again — you should see `New: 0 · Unchanged: N`. That is the ledger doing its job.
  </Step>
</Steps>

<Note>
  **Langfuse Cloud is regional and keys are not valid across regions.** If your project is on US, pass `--host https://us.cloud.langfuse.com` once (it is remembered) or set `LANGFUSE_HOST`. A region mismatch surfaces as `401 Invalid credentials`.
</Note>

Credentials are a **pair**, supplied as one string:

```bash theme={null}
export LANGFUSE_API_KEY="pk-lf-xxxx:sk-lf-yyyy"
# or the vendor-native pair, which is also accepted
export LANGFUSE_PUBLIC_KEY="pk-lf-xxxx"
export LANGFUSE_SECRET_KEY="sk-lf-yyyy"
```

See [`memanto migrate`](/cli/migrate/migrate) for the full option list.

### From the UI

`memanto ui` → **Migrate** → **Langfuse** gives the same thing with checkboxes: **Discover**, capture toggles, threshold fields, **Save settings**, **Preview**, **Sync now**. It reads and writes the same `config.json` and the same ledger as the CLI.

***

## Path 2 — Live SDK handler

Langfuse's Python SDK (v3+) is built on **OpenTelemetry** and attaches its span processor to the global `TracerProvider`. This package attaches a **second** one — so it sees every span your app already produces, with no extra instrumentation and no calls to the Langfuse API.

```
your app ──▶ Langfuse SDK ──▶ OTel TracerProvider ──┬──▶ LangfuseSpanProcessor  ──▶ Langfuse
                                                    └──▶ MemantoLangfuseHandler ──▶ Memanto
```

### Install

```bash theme={null}
pip install langfuse-memanto
```

That is the only install — `memanto` comes with it, and **there is no server to run**. Memories go straight to the Memanto cloud API from your process.

### Quick start from nothing

You need one thing: a [Moorcheh API key](https://console.moorcheh.ai/api-keys).

```bash theme={null}
export MOORCHEH_API_KEY="your-key"
```

Then two lines:

```python theme={null}
from langfuse import Langfuse
from langfuse_memanto import attach

Langfuse()                      # your existing setup
attach(agent_id="my-agent")     # start capturing
```

That is the whole setup. No CLI, no config file, no decorators to add. **The agent is created and activated automatically on the first write.**

```python theme={null}
@observe()
def summarize(doc):
    raise RuntimeError("context window exceeded")
```

becomes:

```
context window exceeded in summarize                      [error]
Langfuse recorded 6 failing 'summarize' observations: context window exceeded.
Seen 6x between 2026-08-07T17:38:14Z and 2026-08-07T17:38:19Z.
tags: langfuse, capture=errors, sig=4c092b52d146, op=summarize
```

<Warning>
  Call `attach()` **after** `Langfuse()`. Before that, OpenTelemetry has only a `ProxyTracerProvider`, which cannot take a span processor — `attach()` raises with that explanation.
</Warning>

### Configuring in code

Anything you would set with the CLI can be passed to `attach()` instead:

```python theme={null}
attach(
    agent_id="my-agent",
    capture=["errors", "slow"],
    latency_ms=5000,                   # slower than 5s is an anomaly
    group_by="metadata.error_code",    # if your messages group poorly
)
```

Precedence is **code → stored profile → default (`errors`)**. So a solo developer never touches the CLI, while a team can manage rules centrally with `--save` and each service just calls `attach(agent_id=...)`. Bad settings raise at `attach()` rather than silently capturing nothing.

### More control

```python theme={null}
from langfuse_memanto import MemantoLangfuseHandler

handler = MemantoLangfuseHandler(agent_id="my-agent", host="https://us.cloud.langfuse.com")
handler.attach()

handler.flush()      # write immediately
handler.stats()      # {'captured': 12, 'written': 2, 'dropped': 0, 'pending': 0}
handler.shutdown()   # flush and stop (also runs at exit)
```

***

## Capture modes

Only `errors` works with **no configuration** — `level` is the one field every Langfuse project populates the same way. Everything else stays inert, and **says so**, until you supply a rule or a budget.

| Mode        | Catches                  | Needs                                    |                       Live?                       |
| ----------- | ------------------------ | ---------------------------------------- | :-----------------------------------------------: |
| `errors`    | `level=ERROR` spans      | nothing                                  |                         ✅                         |
| `slow`      | Latency outliers         | `--latency-ms` or `--latency-percentile` |             ✅ with an absolute budget             |
| `costly`    | Expensive calls          | `--cost-usd` or `--cost-percentile`      | ⚠️ sync only, unless your app sets `cost_details` |
| `low-score` | Traces your evals failed | `--score-fail` rule                      |                    ❌ sync only                    |
| `success`   | Traces your evals passed | `--score-pass` rule                      |                    ❌ sync only                    |

<AccordionGroup>
  <Accordion title="Why low-score and success cannot work live">
    Langfuse scores are attached **after** a trace finishes — nothing in the span carries them. The live handler logs a warning at startup if you enable them, rather than silently capturing nothing. Run `memanto migrate langfuse` periodically to pick them up.
  </Accordion>

  <Accordion title="Why costly is usually sync-only">
    Unless your app explicitly sets `cost_details` on the observation, Langfuse computes cost **server-side after ingestion** — where a span processor cannot see it. Latency is different: it is on the span itself, so `slow` works live.
  </Accordion>

  <Accordion title="Why percentile budgets are ignored live">
    A percentile needs a population to calibrate against. The sync has the whole pulled window; a single span does not. Give `slow` an absolute `latency_ms` for live capture.
  </Accordion>
</AccordionGroup>

### Score rules

Langfuse scores can be **Numeric, Categorical, Boolean, or Text**, with user-defined names and ranges — and the docs state **no convention** for whether higher is better. So you state the direction:

```bash theme={null}
--score-fail 'correctness<0.7'          # numeric, low is bad
--score-fail 'toxicity>0.3'             # numeric, high is bad
--score-fail 'thumbs_up=false'          # boolean
--score-fail 'tone in rude,evasive'     # categorical
--score-pass 'correctness>=0.9'         # what "good" looks like
```

Operators: `<` `<=` `>` `>=` `=` `!=` `in`. Run `--discover` to see your score names, types, and observed ranges first.

## Memory shape

| Field        | Value                                                                                 |
| ------------ | ------------------------------------------------------------------------------------- |
| `type`       | `error` · `learning` (score modes) · `observation` (latency/cost)                     |
| `source`     | `langfuse`                                                                            |
| `provenance` | `imported` — preserves the original Langfuse timestamps                               |
| `source_ref` | Deep link back to a representative trace                                              |
| `confidence` | Rises with occurrence count, capped at 0.95                                           |
| `tags`       | `langfuse`, `capture=<mode>`, `sig=<signature>`, `op=<operation>`, `model=…`, `env=…` |

Everything without a schema slot — occurrence count, models, peak latency, total cost, sample trace ids, first/last seen — goes into a bounded `[Supporting data]` footer, so nothing is lost.

## The sync ledger

`~/.memanto/migrate/langfuse/state.json` maps each signature to the memory it wrote, scoped by **Langfuse project and destination agent**.

That scoping matters: a signature written to agent A tells you nothing about agent B, and two projects can produce identical signatures for unrelated faults. Without it, a sync would skip a write the destination never received.

Because the live handler and the CLI share this ledger, running both is safe — whichever gets there first writes, and the other sees it as already stored.

## Configuration reference

Capture rules live in `~/.memanto/migrate/langfuse/config.json`, written by `--save` or the UI. Only runtime settings come from the environment:

| Variable                             | Required | Default                      | Description                                              |
| ------------------------------------ | :------: | ---------------------------- | -------------------------------------------------------- |
| `MOORCHEH_API_KEY`                   |  **yes** | —                            | Memanto API key. Never logged.                           |
| `MEMANTO_LANGFUSE_AGENT_ID`          |   yes\*  | —                            | Agent that receives the memories. \*Or pass `agent_id=`. |
| `LANGFUSE_PUBLIC_KEY`                |    no    | —                            | Also selects which stored capture profile is used.       |
| `LANGFUSE_HOST`                      |    no    | `https://cloud.langfuse.com` | Use `https://us.cloud.langfuse.com` for US.              |
| `MEMANTO_LANGFUSE_PROJECT`           |    no    | derived                      | Override the capture profile.                            |
| `MEMANTO_LANGFUSE_FLUSH_INTERVAL`    |    no    | `30`                         | Seconds between background flushes.                      |
| `MEMANTO_LANGFUSE_MAX_BUFFER`        |    no    | `100`                        | Flush early at this many pending spans.                  |
| `MEMANTO_LANGFUSE_AUTO_CREATE_AGENT` |    no    | `true`                       | Create + activate the agent on first write.              |
| `MEMANTO_LANGFUSE_SESSION_HOURS`     |    no    | `24`                         | Lifetime of the session the handler opens.               |

## Reliability

* **Nothing runs on your hot path.** `on_end` maps the span and buffers it; grouping and network I/O happen on a daemon thread.
* **Your app is never harmed.** Every entry point swallows its own exceptions — a memory that fails to write will not break your application.
* **Failed writes are retried, not dropped.** A batch that fails is retained and retried; retrying is safe because reconciliation is idempotent. After 4 consecutive failures it is abandoned so a dead backend cannot fill memory.
* **Bounded buffer.** During a storm the buffer stops growing and drops are counted in `stats()["dropped"]`.

## Troubleshooting

<AccordionGroup>
  <Accordion title="401 Invalid credentials">
    Langfuse Cloud is regional and keys are not valid across regions. If your project is on US, use `--host https://us.cloud.langfuse.com` or set `LANGFUSE_HOST`.
  </Accordion>

  <Accordion title="No memories appear from the SDK">
    Check `handler.stats()`. `captured: 0` means no span matched your settings — confirm with `--discover`. `captured > 0, written: 0` means the flush failed:

    ```python theme={null}
    import logging; logging.getLogger("langfuse_memanto").setLevel(logging.DEBUG)
    ```
  </Accordion>

  <Accordion title="A dry run shows New: 0">
    That is correct — those signatures are already synced. Read **Signatures** and **Unchanged** instead. You only see `New` on a project that has not been synced to that agent before.
  </Accordion>

  <Accordion title="Warning: grouping barely collapsed anything">
    Your messages embed values the normalizer did not catch. Pin grouping to a stable field you control:

    ```bash theme={null}
    memanto migrate langfuse --group-by metadata.error_code
    ```
  </Accordion>

  <Accordion title="Titles contain <str> or <n>">
    Those are normalization placeholders standing in for the volatile part of the message — they are the group's identity. That is what lets a thousand variations collapse into one memory.
  </Accordion>
</AccordionGroup>

## Requirements

* Python **3.10+**
* A [Moorcheh API key](https://console.moorcheh.ai/api-keys) (free tier: 100K ops/month)
* For the live handler: `langfuse>=3` in your application. The CLI sync works with **any** Langfuse version, including v2.

<Note>
  Verified end-to-end against **langfuse 3.15.0 and 4.14.3**. If your app is on langfuse v2 (the classic `trace()`/`generation()` API), it predates the OpenTelemetry rewrite — use the CLI sync, or upgrade to v4 to use the live handler.
</Note>

## Shared memory across integrations

`langfuse-memanto` talks to the same Moorcheh-backed Memanto agents as the sibling integrations, so memory written by one is recallable from the others when they share an `agent_id`:

| Integration                                                 | Package            | What it does                                          |
| ----------------------------------------------------------- | ------------------ | ----------------------------------------------------- |
| [`integrations/mcp`](/integrations/mcp)                     | `memanto-mcp`      | MCP server for any MCP-compatible client.             |
| [`integrations/crewai`](/integrations/crewai)               | `crewai-memanto`   | CrewAI tools for multi-agent memory sharing.          |
| [`integrations/hermes-agents`](/integrations/hermes-agents) | `hermes-memanto`   | Memory provider for the Hermes agent.                 |
| `integrations/langfuse`                                     | `langfuse-memanto` | **This** — Langfuse observability signal as memories. |

## Next steps

<CardGroup cols={2}>
  <Card title="memanto migrate" icon="right-left" href="/cli/migrate/migrate">
    Full option reference for the CLI sync, including every capture flag.
  </Card>

  <Card title="Memory Types Reference" icon="layer-group" href="/reference/memory-types">
    What `error`, `learning`, and `observation` mean, and how recall uses them.
  </Card>

  <Card title="Agent Management" icon="user-gear" href="/guides/agent-management">
    Create, activate, and switch the agents these memories land in.
  </Card>

  <Card title="Recall API" icon="magnifying-glass" href="/api-reference/search/recall">
    Read the captured memories back from your own code.
  </Card>
</CardGroup>

***

**Links**

* [`langfuse-memanto` on PyPI](https://pypi.org/project/langfuse-memanto/)
* [Memanto on GitHub](https://github.com/moorcheh-ai/memanto)
* [Langfuse](https://langfuse.com)
