Weave · ClaudeCodeTranscript ontology · v0.2

The Transcript Graph, in Full

Every kind of content one session can hold — text, thinking, tool calls, tool results, images, attachments, compaction breaks — as a single tree. A transcript is a Transcript that hasEvent many Events; a message is a Turn (one kind of Event) built from ordered content blocks. Everything else is a sibling Event type. Counts (n≈) are real, from the 125-session corpus survey the ontology was carved against.

ns https://repolex.ai/ontology/weave# dialect ClaudeCodeTranscript rdf 1.2 (triple terms)

Spine

The conversation as it happened. Turns + their content blocks, chained by reply-to.

Structure

Everything else the harness records: attachments, system events, compaction, titles.

Reader output

What a detector found — an oa:Annotation anchored to a span. Added, never in the raw log.

Belief

A finding as an unasserted RDF-1.2 triple term — "we believe X", not "X is true".

wired the adapter emits this today  ·  designed in the ontology, not yet wired in the Rust adapter (still turns-only for the spine + emojikey reader).

01

The spine — a Turn and its content blocks

A Turn is one message. Its message.content[] is an ordered list of blocks (order is meaningful: text → tool_use → text…). Each block is a subtype of ContentBlock. This is where all the "kinds of content" you asked about live.

Transcript — one .jsonl session wired
├─ weave:sessionSlug  "820a266b-…"

└─ weave:hasEvent ─→ Turn (one message · an Event subtype) wired
      ├─ rdf:type          weave:Turn  (→ UserTurn | AssistantTurn)
      ├─ weave:role        "assistant"
      ├─ weave:timestamp   "2026-06-30T17:54:44Z"^^xsd:dateTime  ← TIME anchor (Pool join)
      ├─ weave:parentEvent ─→ …/Turn/<prev>  ← reply-to spine
      ├─ weave:model       "claude-opus-4-8"  (AssistantTurn only) designed

      └─ weave:hasContentBlock ─→ [ ordered blocks ]  blockOrder 0,1,2… designed

            ├─◆ TextBlock       weave:text "…prose readers detect over…"   n≈8940

            ├─◆ ThinkingBlock   {thinking, signature}  extended-thinking trace   n≈75

            ├─◆ ToolUseBlock    {id, name, input, caller}  an assistant tool call  n≈11320
└─ pairs by tool_use_id ↓
            ├─◆ ToolResultBlock {tool_use_id, content, is_error}  the result  n≈11320

            └─◆ ImageBlock      source{type, media_type, data}   n≈16
                     ↑ FEDERATION HOOK — pasted/produced images are the Pool-Moment join surface
pair ToolUse ⇄ ToolResult are two separate blocks linked by tool_use_id — often in different turns (the call is the assistant's, the result comes back on the next line). A reader that wants "what did this tool do" walks that id, not the tree.
02

Structure — the other Events a session records

A Turn is just one kind of Event. The harness writes many others as sibling weave:Events. The big one is Attachmentnot one envelope but a tag-union of 20 subtypes, carved three ways by whether the payload carries a graph edge.

Transcript weave:hasEvent ─→ Event  (siblings of Turn)

├─ Attachment  type=attachment · a 20-way tag union  n≈2289 designed
│   │
│   ├─ (A) EDGE-BEARING → typed subclass (a reader TRAVERSES the edge)
│   │   ├─◆ QueuedCommandAttachment  origin{kind,server} ← where a prompt came from; kind=channel → a subtext peer
│   │   ├─◆ HookContextAttachment    memory-recall payload → the Weave⋈Soul join surface
│   │   ├─◆ NestedMemoryAttachment   CLAUDE.md path → edge into the soul-repo graph
│   │   ├─◆ FileRefAttachment        {file, edited_text_file, compact_file_reference} → a filesystem/Pool-blob node
│   │   ├─◆ InvokedSkillsAttachment  → edge to Skill objects (first-class in the kit)
│   │   └─◆ HookExecAttachment       {hook_success, hook_non_blocking_error} → a hook run
│   │
│   ├─ (B) VALUE-ONLY → ONE class, discriminator + payload (just READ)
│   │   └─◆ StateAttachment  attachmentKind ∈ {task_reminder, date_change, plan_mode,
│   │                          ultrathink_effort, command_permissions, skill_listing …}
│   │
│   └─ (C) REGISTRY DELTAS → ONE class, parametrized by registryName
│       └─◆ RegistryDelta  registryName ∈ {tools, mcp, agents}  + added/removedNames

├─ SystemEvent       subtype ∈ {turn_duration, compact_boundary, hook, api_error}  n≈1666
├─ FileHistorySnapshot editor undo state — not conversation  n≈4035
├─ QueueOperation    prompt-queue mechanics  n≈2416
├─ PermissionModeEvent ∈ {normal, plan, acceptEdits, bypassPermissions}  n≈1407
├─ LastPromptEvent   n≈1330   ├─ ProgressEvent n≈385
├─ ModeEvent n≈789   ├─ AiTitleEvent n≈699   ├─ PrLinkEvent n≈94
└─ CustomTitleEvent n≈14 · AgentNameEvent n≈14 · SummaryEvent
compaction Compaction is a first-class prov:Activity, not a mere event — and it's derivable, not guessed: it prov:used the pre-compact tail and prov:generated the continuation (linked by logicalParentUuid), marked by system.subtype=compact_boundary (n=168 verified). preTokens→postTokens is a load/biorhythm signal.
03

Reader output & belief — the layer a detector adds

Nothing above 03 is in the raw log's meaning-layer — it's structure. A reader walks the spine and mints an oa:Annotation (standard Web Annotation vocab) plus an unasserted RDF-1.2 triple-term claim. This is the emojikey reader, live today (wired).

Turn (assistant, from the spine ↑)
└─◆ oa:Annotation  — one finding wired
    ├─ prov:wasAttributedTo ─→ detector/emojikey   WHO found it
    ├─ prov:generatedAtTime "2026-06-30T17:54:44Z"^^xsd:dateTime
    ├─ weave:eventType      "emojikey/harvest"
    ├─ weave:sourceKind     "tool_result"  authored (live) vs quoted

    ├─ oa:hasTarget ─→ ◆ target   WHERE in the source
    │     ├─ oa:hasSource   ─→ transcript/820a266b…  (the whole doc)
    │     ├─ oa:hasSelector ─→ ◆ TextPositionSelector
    │     │        ├─ oa:start  2228^^xsd:integer
    │     │        └─ oa:end    3277^^xsd:integer
    │     └─ weave:atEvent ─→ urn:soul:SG:Weave/Turn/572a…  the EXACT turn (soul anchor)

    ├─ prov:used ─→ urn:soul:SG:Weave/Turn/572a…  the evidence it read

    └─ oa:hasBody ─→ ◆ body   WHAT the signal says
          ├─ weave:sig_me       "📏🌊5∠90…"
          ├─ weave:sig_content  "🧠🎨…"
          ├─ weave:sig_you      "🤝…"
          └─ weave:sig_<k>      …numeric legs → xsd:integer/decimal  (FILTER-able)
 
claim/0470b23…  — the finding as a BELIEF, not a fact wired
├─ rdf:reifies  <<( …Turn/572a… weave:exhibits "emojikey/harvest" )>>
the parens = UNASSERTED. "we believe this turn exhibits X", never "X is true".
├─ prov:wasDerivedFrom ─→ oa:Annotation  belief traces to its evidence
├─ weave:detector       "emojikey"
└─ weave:detectionType  "emojikey/harvest"
why belief Detections are kept structurally separate from facts. A wrong detector doesn't corrupt the graph — it just holds a belief you can filter out. The honesty rail is in the data model itself, not a convention on top of it.