For the complete documentation index, see llms.txt. This page is also available as Markdown.

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; 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 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.

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:

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 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:

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 that selects one scalar value from each source trace:

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:

The REST request uses the same structure:

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.

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 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, 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:

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:

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. 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 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:

Last updated

Was this helpful?