Skip to content

[G1] First-class fork / resume / continuation relationships with cross-file reconciliation (Claude and Codex) #170

Closed

Description

@willwashburn

Part of #160 (group 1 — independent).

Problem

session_relationships models delegation only. RelationshipType is 'delegated' (sdk-ts/src/contracts.ts:220); the only other relationship value written is materialized_local (Claude remote→local, crates/ai-hist/src/lib.rs:2887-2907). Resumed, forked, and continued sessions are stored as unrelated sessions, so a consumer cannot roll a /resumed conversation up to its origin, and burn's continuity analytics (burn flow rail continuity, summary --by-relationship) have nothing to read.

burn detects all of these today in crates/relayburn-sdk/src/reader/claude/relationships.rs and emits SessionRelationshipRecord { relationship_type: root|continuation|fork|subagent, related_session_id, source_session_id, source_version, ts } (reader/types.rs:497-520):

  • Explicit fields on Claude records: forkSessionId, continuedFromSessionId, sourceSessionId (relationships.rs:45-114).
  • /resume and /continue markers in user text → local continuation (relationships.rs:259-321; fixture resume-marker.jsonl).
  • Cross-file reconciliation (relationships.rs:405-522): builds a uuid → file_session_id map across all parsed files; a file whose first_parent_uuid belongs to another file is a continuation; ≥2 sessions sharing a foreign sourceSessionId are forks (fixtures cross-file-parent, original-session, fork-branch-a, fork-branch-b, explicit-line-relationships).
  • Evidence accumulated per file: ClaudeRelationshipEvidence { file_session_id, first_ts, in_log_session_ids, source_version, first_parent_uuid, seen_uuids, has_resume_marker, resume_target_session_id, explicit_continuation_target_session_ids, explicit_fork_target_session_ids } (relationships.rs:23-38).
  • file_session_id derivation honours only explicit signals (fileSessionId, then the transcript path basename) — never the opened path (relationships.rs:549-569); a transcript whose in-log sessionIds differ from its file name is itself relationship evidence.
  • Codex: resume is currently handled implicitly by the token baseline (lib.rs:2260-2263); a resumed rollout's session_meta may carry the prior thread id — characterize on the fixture and record it.

relayhistory already stores parent_id (parentUuid) per event and has session_identity_correlations (crates/ai-hist-core/src/lib.rs:822) for remote/local pairs, so the substrate is close.

Scope

  1. Extend session_relationships.relationship to delegated | materialized_local | continuation | fork | resume and add origin_session_id (the sourceSessionId a fork/continuation came from, when distinct from the parent) + evidence_ref usage: the uuid that linked the files, the marker text, or the explicit field name. Keep the PK (source, parent_session_id, relationship_uid); relationship_uid = "continuation:<child>" etc.
  2. Claude parser: read forkSessionId, continuedFromSessionId, sourceSessionId, /resume//continue markers, and accumulate the per-file evidence struct above into a new session_continuity_evidence table (source, session_id, file_session_id, first_parent_uuid, first_ts, in_log_session_ids_json, resume_target, explicit_targets_json, source_version), keyed per transcript, so reconciliation can run incrementally during targeted hydration (one file at a time) rather than only during a full sync with every file in memory.
  3. Reconciliation pass: after any Claude hydration/sync, resolve first_parent_uuid against session_events.event_uid/message_id across sessions and write continuation/fork rows; record RELATIONSHIP_CONTINUITY_UNRESOLVED diagnostics when the parent uuid is not (yet) indexed rather than guessing.
  4. Codex: characterize resume on a real resumed rollout (fixture) and record a resume relationship when session_meta names a prior thread; otherwise document that Codex resume is not observable.
  5. Traversal: session_tree / session_parents accept a relationship_kinds filter defaulting to delegated only (so existing tree semantics are unchanged) and can include continuity edges; getSessionRelationships returns them under a new continuity array. Bump SESSION_RELATIONSHIP_CONTRACT_VERSION (crates/ai-hist-core/src/relationships.rs:15); update contracts.ts, normalization.ts, MCP get_session_relationships, docs/architecture.md "Delegation topology" (rename to "Session topology").

Acceptance

  • Corpus: fork-branch-a + fork-branch-b each get a fork row pointing at original-session; cross-file-parent gets a continuation row; resume-marker gets a resume row; explicit-line-relationships yields the explicit rows with evidence_ref naming the field.
  • Hydrating fork-branch-a before original-session exists yields an RELATIONSHIP_CONTINUITY_UNRESOLVED diagnostic, and hydrating original-session afterwards resolves it without re-hydrating fork-branch-a.
  • getSessionTree default output is byte-identical to before for delegation-only databases (existing relationships.test.ts unchanged).

Out of scope

Similarity-based linking (explicitly forbidden by docs/architecture.md: "title or prompt similarity is never used to infer one").

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions