> For the complete documentation index, see [llms.txt](https://docs.zenml.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.zenml.io/kitaru/import-your-traces/importing-sessions.md).

# Kitaru JSONL

Import provider traces or portable Kitaru session JSONL into a workspace.

Kitaru importers convert exported trace data into session graphs. Provider importers decode source records, join related traces into sessions, order turns, reconstruct node relationships, and project common fields for the UI while preserving source inputs and outputs.

Use a provider importer for Langfuse, LangSmith, Braintrust, Logfire, or Arize Phoenix data. For Mastra full trace exports, follow the registration and import workflow in the [Mastra guide](/kitaru/adapters/mastra.md); that importer is not a server default. Use the `kitaru-jsonl` importer when your producer already emits the Kitaru session and node contract.

## The portable session contract

Each imported session contains session fields and a list of nodes. A session is the user-visible execution or conversation. A node is one recorded model call, tool call, subagent call, or span.

| Session field            | Type                                    | Meaning                                                                                            |
| ------------------------ | --------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `status`                 | `in_progress`, `completed`, or `failed` | Final source status.                                                                               |
| `name`                   | string or null                          | Display name.                                                                                      |
| `inputs`                 | any JSON value                          | Complete session input. Provider importers use a versioned `turns` object for multi-turn sessions. |
| `outputs`                | any JSON value                          | Final session output.                                                                              |
| `error`                  | string or null                          | Failure message.                                                                                   |
| `started_at`, `ended_at` | ISO 8601 timestamp or null              | Session time range.                                                                                |
| `external_id`            | string                                  | Stable identity in the source system. Kitaru uses it with `imported_from` for deduplication.       |
| `metadata`               | JSON object                             | Source identity, normalization warnings, and user metadata.                                        |
| `imported_from`          | string or null                          | Source importer. Kitaru sets this from the selected importer rather than the JSONL record.         |
| `framework`              | string or null                          | Agent framework when the trace identifies one, such as `pydantic-ai` or `langgraph`.               |
| `nodes`                  | node array                              | Flat indexed nodes.                                                                                |

Each node uses the fields below. Optional fields can be omitted or set to null.

| Node field                                   | Type                                                | Meaning                                                                                                                             |
| -------------------------------------------- | --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `index`                                      | integer                                             | Identity of the node within the session import, unique per session.                                                                 |
| `parent_index`                               | integer or null                                     | Index of the parent node.                                                                                                           |
| `links`                                      | link array                                          | Links to other nodes of the session, each with the target's `external_id` and a `kind`.                                             |
| `external_id`, `trace_id`                    | string or null                                      | Source node and trace identities.                                                                                                   |
| `node_type`                                  | `llm_call`, `tool_call`, `subagent_call`, or `span` | Work represented by the node.                                                                                                       |
| `name`                                       | string                                              | Display name.                                                                                                                       |
| `status`                                     | `in_progress`, `completed`, or `failed`             | Node status.                                                                                                                        |
| `error`                                      | string or null                                      | Failure message.                                                                                                                    |
| `started_at`, `ended_at`                     | ISO 8601 timestamp or null                          | Node time range.                                                                                                                    |
| `input_text_selector`                        | string or null                                      | RFC 6901 JSON Pointer selecting the primary human-readable text inside `inputs`.                                                    |
| `output_text_selector`                       | string or null                                      | RFC 6901 JSON Pointer selecting the primary human-readable text inside `outputs`.                                                   |
| `system_prompt_selector`                     | string or null                                      | RFC 6901 JSON Pointer selecting the system prompt inside `inputs`.                                                                  |
| `reasoning_selectors`                        | string array                                        | RFC 6901 JSON Pointers selecting visible reasoning strings inside `outputs`.                                                        |
| `inputs`, `outputs`                          | any JSON value                                      | Complete source payloads. Importers preserve message history, tool arguments, multimodal parts, and provider-specific content here. |
| `requested_model`, `model`, `model_provider` | string or null                                      | Requested model, served model, and model provider.                                                                                  |
| `tokens`                                     | object or null                                      | Input, output, cached input, and reasoning token counts when reported.                                                              |
| `cost`                                       | decimal or null                                     | Recorded or estimated call cost.                                                                                                    |
| `model_params`                               | object or null                                      | Model request parameters.                                                                                                           |
| `tool_name`, `subagent_id`                   | string or null                                      | Tool or subagent identity for the matching node type.                                                                               |
| `attributes`                                 | any JSON value                                      | Span attributes retained for diagnostics.                                                                                           |
| `metadata`                                   | JSON object                                         | Bounded source metadata.                                                                                                            |

Text selectors avoid copying potentially large values into separate columns. A selector is present only when the importer can identify one relevant string in the corresponding payload. A client resolves that [RFC 6901 JSON Pointer](https://www.rfc-editor.org/rfc/rfc6901.html) when it loads the node payload and can show the complete `inputs` or `outputs` value for inspection. The selectors remain available in node list responses without loading the payload columns. `system_prompt_selector` resolves against `inputs`. A null selector means the importer could not choose one text value without guessing. The empty string is the JSON Pointer for the complete payload, which is useful when the payload itself is the selected string.

`reasoning_selectors` points at visible reasoning text only, wherever it lives inside `outputs`. A client resolves each pointer and joins the resulting strings with newlines, in order. Redacted, encrypted, or unavailable reasoning leaves the list empty, while the provider payload stays in `inputs` or `outputs`. Token usage can also include `reasoning_tokens` when a provider reports the count.

## Create Kitaru JSONL

Write one session object per line. The `kitaru-jsonl` importer validates every field and rejects unknown fields. Invalid lines are reported independently, so valid sessions in the same upload can still import.

The formatted object below represents one JSONL record. Serialize it onto one line in the file.

```json
{
  "status": "completed",
  "name": "Weather request",
  "inputs": {"question": "What is the weather in Delft?"},
  "outputs": {"answer": "Delft is rainy and 18 C."},
  "started_at": "2026-07-22T10:00:00Z",
  "ended_at": "2026-07-22T10:00:01Z",
  "external_id": "weather-session-42",
  "metadata": {"environment": "production"},
  "framework": "pydantic-ai",
  "nodes": [
    {
      "index": 0,
      "parent_index": null,
      "links": [],
      "external_id": "model-call-42",
      "trace_id": "trace-42",
      "node_type": "llm_call",
      "name": "answer weather question",
      "status": "completed",
      "started_at": "2026-07-22T10:00:00Z",
      "ended_at": "2026-07-22T10:00:01Z",
      "input_text_selector": "/1/content",
      "output_text_selector": "/0/content",
      "system_prompt_selector": "/0/content",
      "reasoning_selectors": ["/1/content"],
      "inputs": [{"role": "system", "content": "Answer in one sentence."}, {"role": "user", "content": "What is the weather in Delft?"}],
      "outputs": [{"role": "assistant", "content": "Delft is rainy and 18 C."}, {"role": "reasoning", "content": "The weather tool reports rain and a temperature of 18 C."}],
      "model": "claude-haiku-4-5-20251001",
      "model_provider": "anthropic",
      "tokens": {"input_tokens": 24, "output_tokens": 11, "cached_input_tokens": 0, "reasoning_tokens": 0},
      "attributes": {},
      "metadata": {}
    }
  ]
}
```

Node indexes do not need to be contiguous, and a parent may carry a higher index than its child. A node without an `external_id` gets `node-<index>`, which is also how a link names it.

## Import a file

The `kitaru-jsonl` importer has no fetch entrypoint, so it only accepts uploaded files. FILE is always required, and `--since`, `--until`, `--trace-id`, and `--query` do not apply.

The session import command uploads the file, resolves an exact importer and agent version, and creates an import job:

```bash
kitaru session import sessions.jsonl \
  --importer kitaru/kitaru-jsonl@latest \
  --agent customer-service@latest \
  --media-type application/x-ndjson \
  --wait
```

Use `--tag` with `--wait` to tag every created session. Use `--join-on` to group provider traces by a source value. Use `--params` for other provider-specific settings. Use `--max-sessions` to stop the import after it creates a set number of sessions. Use `--evaluator` to score every imported session once the import finishes, `--evaluator-params` to pass parameters to a selected evaluator, and `--evaluator-connection` to select credentials for it. Use `--analyzer` to run an [analyzer](/kitaru/core-concepts/analyzers.md) over every imported session once the import finishes, `--analyzer-params` to pass parameters to a selected analyzer, and `--analyzer-connection` to select credentials for it:

```bash
kitaru session import sessions.jsonl \
  --importer kitaru/kitaru-jsonl@latest \
  --agent customer-service@latest \
  --evaluator accuracy@latest \
  --evaluator-params 'accuracy@latest={"threshold": 0.8}' \
  --evaluator-connection accuracy@latest=model-provider-prod \
  --analyzer session-outcomes@latest \
  --analyzer-params 'session-outcomes@latest={"min_count": 5}' \
  --analyzer-connection session-outcomes@latest=model-provider-prod \
  --wait
```

The command prints the created import id and the job running it. One evaluator task runs per imported session and evaluator, and one analyzer task runs per analyzer over every session the import created, so a failed evaluator or analyzer marks the job failed while the import itself still records how many sessions it created.

## Join provider traces into sessions

Providers often record one conversation turn as one trace. Importers that support conversation grouping combine related traces into one Kitaru session, then order the traces by start time with a stable trace-ID tie-breaker. The Mastra importer instead preserves each invocation as a separate session and does not accept `--join-on`.

Default grouping uses the provider's native conversation or session identifier. When that identifier is absent, each trace becomes one session. Use `--join-on` when the export carries the shared session identity in another field.

The option takes an [RFC 6901 JSON Pointer](https://www.rfc-editor.org/rfc/rfc6901.html) that selects one scalar value from each source trace:

```bash
kitaru session import langfuse-observations.jsonl \
  --importer kitaru/langfuse@latest \
  --agent customer-service@latest \
  --join-on '/metadata/customer/case_id' \
  --wait
```

The example reads the scalar at `/metadata/customer/case_id`. Five traces with the value `case-42` become five ordered turns in the same Kitaru session. Traces with a different value form a different session.

Escape source keys according to RFC 6901. Use `~1` for `/` and `~0` for `~`. For example, `/metadata/customer~1case~0id` selects the key `customer/case~id` inside `metadata`.

The pointer root depends on the importer:

| Importer   | Pointer root                                                                | Example                       |
| ---------- | --------------------------------------------------------------------------- | ----------------------------- |
| Braintrust | Each raw trace-root record                                                  | `/metadata/customer~1case_id` |
| Langfuse   | Observation records belonging to one trace; every selected value must agree | `/metadata/customer/case_id`  |
| LangSmith  | Each raw trace-root run                                                     | `/extra/metadata/thread_id`   |

The selected value must be a non-empty string, number, or boolean. A missing, conflicting, object, or array value produces an isolated failure for that trace. Kitaru does not silently place the trace into a fallback session. Imported metadata records explicit grouping provenance under `braintrust.join_on`, `langfuse.join_paths`, or `langsmith.join_paths`.

### SDK and REST

The CLI validates `--join-on` and adds it to the importer parameter object, resolves each `--evaluator` plus any matching `--evaluator-connection` into an entry of the `evaluators` list, and resolves each `--analyzer` plus any matching `--analyzer-connection` into an entry of the `analyzers` list. SDK callers pass the same `join_on` parameter, evaluator configs, and analyzer configs directly:

```python
from kitaru.api_models.v1.imports import ImportCreateRequest
from kitaru.api_models.v1.plugin import AnalyzerConfig, EvaluatorConfig

created_import = await client.imports.create(
    ImportCreateRequest(
        importer="kitaru/langfuse",
        version=1,
        agent_id=agent_id,
        agent_version_id=agent_version_id,
        payload_blob_id=blob_id,
        params={"join_on": "/metadata/customer/case_id"},
        evaluators=[
            EvaluatorConfig(
                evaluator="accuracy",
                params={"threshold": 0.8},
                connection_id=evaluator_connection_id,
            )
        ],
        analyzers=[
            AnalyzerConfig(
                analyzer="session-outcomes", connection_id=analyzer_connection_id
            )
        ],
    )
)
```

The REST request uses the same structure:

```json
{
  "importer": "kitaru/langfuse",
  "version": 1,
  "agent_id": "00000000-0000-0000-0000-000000000000",
  "agent_version_id": "00000000-0000-0000-0000-000000000001",
  "payload_blob_id": "00000000-0000-0000-0000-000000000002",
  "params": {"join_on": "/metadata/customer/case_id"},
  "evaluators": [{"evaluator": "accuracy", "params": {"threshold": 0.8}}],
  "analyzers": [
    {
      "analyzer": "session-outcomes",
      "connection_id": "00000000-0000-0000-0000-000000000003"
    }
  ]
}
```

Send this object to `POST /api/v1/imports`. Each `evaluators` entry names an evaluator, an optional `version` that resolves to the latest version when omitted, and `params`. Each `analyzers` entry does the same for an analyzer and can select a `connection_id`. Without one, the analyzer uses the default connection for its provider when available. The response is the import, whose `job_id` names the job running it. The server stores `params` and the resolved evaluators and analyzers on the import, the worker includes the params in `ImportTaskDetails`, and the task process calls the selected importer as `parse(payload, params)`. Once the import finishes, every listed evaluator scores every imported session and every listed analyzer runs once over the sessions the import created.

Read an import back with `GET /api/v1/imports/{import_id}` or `client.imports.get(import_id)`, and list imports with `GET /api/v1/imports` or `client.imports.list(...)`, filterable on `id`, `agent_id`, and `job_id`.

```bash
kitaru import list --output json
kitaru import get <import-id> --output json
```

Existing integrations can continue to send `params.join_on` as a dotted path. The explicit CLI option accepts JSON Pointer syntax only. Langfuse also retains its older `join_path` plus `join_key` parameters for compatibility, but new integrations should use `join_on`.

## What provider importers normalize

Select the built-in [post-import insights](/kitaru/import-your-traces/post-import-insights.md) analyzers explicitly to produce deterministic cards, OpenAI-backed cards, or both. They need a worker that claims analyzer tasks; only the OpenAI analyzer requires model credentials. The linked guide covers local and self-hosted setup and reading results.

Provider importers apply the same output contract to different source formats:

| Source     | Accepted shape                                         | Default grouping                                    |
| ---------- | ------------------------------------------------------ | --------------------------------------------------- |
| Langfuse   | Trace, observation, and ingestion-event JSON or JSONL  | `sessionId`, then `traceId`                         |
| LangSmith  | Run-query and bulk-export JSON or JSONL                | Known thread metadata paths, then `trace_id`        |
| Braintrust | Project-log and UI JSON exports                        | Known session or conversation fields, then trace ID |
| Mastra     | Full `getTrace` JSON response or an array of responses | No grouping; each trace is one invocation           |
| Kitaru     | One portable Kitaru session per JSONL line             | No grouping; each line is one session               |

Normalization includes source identity, parent-child graph reconstruction, deterministic ordering, status and error mapping, model fields, token counts, cost, tool arguments and results, text selectors, reasoning selectors, and framework detection. Source payloads remain in `inputs` and `outputs`. Session metadata reports normalization warnings and source completeness.

Framework detection only sets `framework` when trace metadata identifies one supported framework without conflict. Unknown or sparse traces keep the field null.

## Inspect failures

The import job result reports created, skipped, and failed counts plus a bounded failure sample. The same counts land in the `stats` field of the import once parsing completes, and a parse failure lands in its `error` field. `stats` records the parse outcome on its own, so an import whose evaluators or analyzers fail keeps its counts while the job reports the failed task. Every session created by an import carries the `import_id` it came from. Reimporting the same `(imported_from, external_id)` pair skips the duplicate.

## No importer for your provider

You have two ways in, and neither requires waiting for us to ship an importer.

**Convert to Kitaru JSONL.** Write out [Kitaru JSONL](#create-kitaru-jsonl), one session object per line, exactly the contract above. This is the right choice for a one-off backfill or an export you can transform with a script. Nothing gets installed or registered.

**Write an importer.** Worth it when the conversion is ongoing, or when the source needs real normalization rather than a field rename. The contract is one function:

```python
Parser = Callable[[bytes, dict[str, Any]], Iterator[ImportedSession | ImportFailure]]
```

That is the whole interface. You receive the uploaded bytes and the `--params` object, then yield one `ImportedSession` per session you recognize, or an `ImportFailure` for a record you cannot parse. A yielded failure isolates that record instead of failing the whole import:

```python
from kitaru.api_models.v1.imports import ImportFailure
from kitaru.task.importer import ImportedSession


def parse(
    content: bytes, params: dict[str, Any]
) -> Iterator[ImportedSession | ImportFailure]:
    for line_number, line in enumerate(content.decode("utf-8").splitlines(), start=1):
        try:
            yield ImportedSession.model_validate(transform(json.loads(line)))
        except ValueError as exc:
            yield ImportFailure(line=line_number, external_id=None, error=str(exc))
```

Three things matter most because they are where custom importers usually go wrong:

* **`external_id` is your identity, and it must be stable.** Kitaru deduplicates on `(imported_from, external_id)`, so a re-import is only safe if the id does not move between runs. Derive it from the source's own identifier, never from a row number or a timestamp.
* **Decide session boundaries deliberately.** One `ImportedSession` should be one end-to-end run; see [what a session is](/kitaru/core-concepts/agents-and-sessions.md). If your source splits a run across records, join them in the parser.
* **Yield failures, don't raise them.** An exception ends the import; an `ImportFailure` costs you one record and keeps the rest.

The shipped importers are the reference: `plugins/packages/jsonl-importer` is the smallest at under 80 lines, and the Langfuse one shows real normalization. The `kitaru-importer-builder` [agent skill](/kitaru/getting-started/setup.md) exists for this job: it turns a representative export into a locally validated importer, keeps the mapping from source evidence to normalized sessions explicit so you can see what is preserved, approximated, or unavailable, and finishes locally until you approve registration:

```bash
npx skills add zenml-io/kitaru-skills
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.zenml.io/kitaru/import-your-traces/importing-sessions.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
