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.
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.
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:
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:
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_idis 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
ImportedSessionshould 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
ImportFailurecosts 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?