Adapters and the trace import contract
June 4, 2026 · View on GitHub
lessonweaver mines a single internal shape: the TraceBundle.
Real traces arrive in many formats (sibling tools, OpenTelemetry spans, CI
logs, custom loggers). The TraceImporter protocol (issue #52) defines one
small, dependency-free contract every importer follows, so importers stay
swappable and testable instead of being designed ad hoc.
The TraceImporter protocol
from typing import Any, Protocol, runtime_checkable
@runtime_checkable
class TraceImporter(Protocol):
def can_import(self, source: dict[str, Any]) -> bool: ...
def import_trace(self, source: dict[str, Any]) -> TraceBundle: ...
can_import— cheap recognition. ReturnTrueonly for payloads this importer understands (a schema marker, or a structural fingerprint). It must not raise.import_trace— convert a recognized payload into aTraceBundle. RaiseValueErrorwith a human-readable message for a recognized-but-invalid payload.
Importers are deterministic and dependency-free: no network, no LLM, no importing the source system. They map a dict onto the schema.
Built-in importers (core)
| Importer | Input | Notes |
|---|---|---|
DictTraceImporter | canonical lessonweaver trace JSON | load_trace_bundle delegates to it |
FailureCaseImporter | replayable failure case artifact | governed path for issue #82 |
DictTraceImporter makes the canonical loader a special case of the protocol:
load_trace_bundle reads the JSON file, then calls import_trace.
Normalization steps (recommended)
When writing an adapter for a new format:
- Identify the trace id, task, and source. Every bundle needs a stable
trace_id. - Map events. Translate each source record to a
TraceEventType. The conservative detector keys on these in particular:- a human fix →
human_correction - a failed grade/check →
evaluation_resultwithstatus="failed" - a failure then a recovery →
errorfollowed byretry(with a successful outcome), ortool_callfailure then success - a workflow misstep →
workflow_step(+ a followingerror)
- a human fix →
- Set the outcome. Use
corrected_by_humanwhen a human fix is present, otherwisesuccess/failure/unknown. - Preserve provenance. Put source-specific identifiers in
metadata(on the bundle and/or events). Unknown source fields can be dropped or carried inmetadata; lessonweaver ignores unknown keys. - Keep it optional. Concrete adapters for external systems live in
examples/, never in core (see interoperability).
Required vs optional bundle fields
| Field | Required | Default if omitted |
|---|---|---|
trace_id | yes | — |
source | yes (canonical) | importer-supplied |
task | recommended | "" |
events | yes (canonical) | [] |
outcome | recommended | "unknown" |
metadata | no | {} |
The failure case artifact (issue #82)
FailureCaseImporter accepts a replayable failure artifact — mirroring the
planned weaver-spec FailureCaseArtifact (dgenio/weaver-spec#72) — and maps it
to a bundle so the normal loop applies. Recognized keys:
| Key | Meaning |
|---|---|
failure_id (or id) | stable id → trace_id (required) |
failure.summary / failure.detail | becomes a failed evaluation_result event |
failure.severity | preserved in provenance |
correction.summary | becomes a human_correction event |
replay.ref / replay.reproducible | preserved in provenance |
task, source, outcome, schema | optional |
Provenance is stored under metadata["failure_case"] and propagated onto every
resulting candidate by candidates_from_failure_case. See
examples/failure_cases/.
Known future adapter candidates
- OpenTelemetry spans — design sketched in
design/opentelemetry-import.md. - Sibling tools — agent-kernel ActionTrace, ChainWeaver flow-failure, and
vibeguard finding adapters live in
examples/interop_adapters/. - Claude Code hooks, Pipecat post-call JSON, CI logs.