Skip to content
5 changes: 2 additions & 3 deletions src/main/tape/application/common.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { TAPE_INCARNATION_META_KEY, type DeepChatTapeEntryRow } from '../domain/entry'

const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/
import { CANONICAL_UUID_PATTERN } from '../domain/primitives'

export function parseJsonObject(raw: string): Record<string, unknown> {
try {
Expand All @@ -24,7 +23,7 @@ export function readCanonicalTapeIncarnationId(row: DeepChatTapeEntryRow): strin
return null
}
const value = parseJsonObject(row.meta_json)[TAPE_INCARNATION_META_KEY]
return typeof value === 'string' && UUID_PATTERN.test(value) ? value : null
return typeof value === 'string' && CANONICAL_UUID_PATTERN.test(value) ? value : null
}

export function parseJsonValue(raw: string): unknown {
Expand Down
7 changes: 0 additions & 7 deletions src/main/tape/application/factPersistence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
} from '@/tape/domain/facts'
import type { DeepChatTapeEntryRow } from '@/tape/domain/entry'
import type { TapeBootstrapStore, TapeEntryStore } from '@/tape/ports/storage'
import { buildEffectiveTapeView } from '@/tape/domain/effectiveView'
import {
parseAssistantBlocks,
parseTapeJsonObject,
Expand Down Expand Up @@ -612,9 +611,3 @@ export function appendMessageRetractionToTape(

return 1
}

export function tapeEntriesToEffectiveMessageRecords(
rows: DeepChatTapeEntryRow[]
): ChatMessageRecord[] {
return buildEffectiveTapeView(rows, { includePending: true }).messageRecords
}
2 changes: 1 addition & 1 deletion src/main/tape/application/factService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ export class TapeFactService
}

getMessageRecords(sessionId: string): ChatMessageRecord[] {
return buildEffectiveTapeView(this.table.getEffectiveViewInputRows(sessionId), {
return buildEffectiveTapeView(this.table.getEffectiveMessageInputRows(sessionId), {
includePending: true
}).messageRecords
}
Expand Down
5 changes: 3 additions & 2 deletions src/main/tape/application/lineageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import {
} from '../domain/entry'
import type { TapeApplicationProviders } from '../ports/application'
import { parseJsonObject, parseJsonValue } from './common'
import { computeTapeIdentity, TAPE_IDENTITY_PATTERN } from '../domain/tapeIdentity'
import { SHA256_HEX_PATTERN } from '../domain/primitives'
import { computeTapeIdentity } from '../domain/tapeIdentity'

type TapeLineageProviders = Pick<
TapeApplicationProviders,
Expand Down Expand Up @@ -141,7 +142,7 @@ function parseSubagentTapeLink(row: DeepChatTapeEntryRow): ParsedSubagentTapeLin
(linkVersion === 1 && childTapeIdentity === undefined) ||
(linkVersion === SUBAGENT_TAPE_LINK_VERSION &&
typeof childTapeIdentity === 'string' &&
TAPE_IDENTITY_PATTERN.test(childTapeIdentity))
SHA256_HEX_PATTERN.test(childTapeIdentity))
if (
row.kind !== 'event' ||
row.name !== SUBAGENT_TAPE_LINK_EVENT_NAME ||
Expand Down
16 changes: 9 additions & 7 deletions src/main/tape/application/viewReplayService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ import type {
} from '@shared/types/tape-replay'
import { SUMMARY_ANCHOR_NAMES, type DeepChatTapeEntryRow } from '../domain/entry'
import { buildEffectiveTapeView } from '../domain/effectiveView'
import { readTapeMessageRetractionId, tapeEntryToMessageRecord } from '../domain/effectiveSemantics'
import {
isEffectiveMessageInputRow,
readTapeMessageRetractionId,
tapeEntryToMessageRecord
} from '../domain/effectiveSemantics'
import {
collectEntryIds,
hashString,
Expand Down Expand Up @@ -358,12 +362,10 @@ export class TapeViewReplayService {
}
}

const messageSourceRows = rows.filter(
(row) => row.kind === 'message' || (row.kind === 'event' && row.name === 'message/retracted')
)
for (const { entryId, record } of buildEffectiveTapeView(messageSourceRows, {
includePending: true
}).messageEntries) {
for (const { entryId, record } of buildEffectiveTapeView(
rows.filter(isEffectiveMessageInputRow),
{ includePending: true }
).messageEntries) {
entryIdByMessageId.set(record.id, entryId)
if (record.role === 'user') {
messageContentHashByMessageId.set(record.id, hashJsonData(record.content))
Expand Down
100 changes: 70 additions & 30 deletions src/main/tape/domain/effectiveSemantics.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,58 @@
import type { AssistantMessageBlock, ChatMessageRecord } from '@shared/types/agent-interface'
import type { DeepChatTapeEntryRow } from './entry'
import type { DeepChatTapeEntryKind, DeepChatTapeEntryRow } from './entry'

const TERMINAL_TAPE_TOOL_STATUSES = new Set(['success', 'error'])

export const TAPE_MESSAGE_RETRACTED_EVENT_NAME = 'message/retracted'

/**
* Kinds an effective-state reader may select wholesale. `event` is excluded because the only event
* the effective view acts on is `message/retracted`, which every input set already selects by
* name (listing `event` would return those rows twice); `context` rows are behavioural evidence
* the fold skips outright.
*/
export type EffectiveInputKind = Exclude<DeepChatTapeEntryKind, 'event' | 'context'>

/**
* Rows that can change effective message/tool state or anchor positions, plus `message/retracted`
* events. Every other row (ViewManifests, Journal, provider attempts, contracts, tool-surface
* provenance, indicators) is evidence the effective view only passes through, so readers that need
* effective state skip it at the store. `TapeEntryStore.getEffectiveViewInputRows` selects by the
* same constant.
*/
export const EFFECTIVE_VIEW_INPUT_KINDS = [
'message',
'tool_call',
'tool_result',
'anchor'
] as const satisfies readonly EffectiveInputKind[]

/**
* Rows that can change effective message/tool state or anchor positions. Every other row
* (ViewManifests, Journal, provider attempts, contracts, tool-surface provenance, indicators) is
* evidence the effective view only passes through, so readers that need effective state can skip
* it at the store. Must stay in sync with `TapeEntryStore.getEffectiveViewInputRows`.
* The subset that decides `messageRecords`/`messageEntries`: message rows plus the retraction
* events that remove them. Tool rows only join onto messages and anchors only pass through, so
* readers that need effective messages alone skip both at the store.
* `TapeEntryStore.getEffectiveMessageInputRows` selects by the same constant.
*/
export const EFFECTIVE_MESSAGE_INPUT_KINDS = [
'message'
] as const satisfies readonly EffectiveInputKind[]

function isEffectiveInputRow(
row: { kind: string; name: string | null },
kinds: readonly string[]
): boolean {
return (
kinds.includes(row.kind) ||
(row.kind === 'event' && row.name === TAPE_MESSAGE_RETRACTED_EVENT_NAME)
)
}

export function isEffectiveViewInputRow(row: { kind: string; name: string | null }): boolean {
switch (row.kind) {
case 'message':
case 'tool_call':
case 'tool_result':
case 'anchor':
return true
case 'event':
return row.name === 'message/retracted'
default:
return false
}
return isEffectiveInputRow(row, EFFECTIVE_VIEW_INPUT_KINDS)
}

export function isEffectiveMessageInputRow(row: { kind: string; name: string | null }): boolean {
return isEffectiveInputRow(row, EFFECTIVE_MESSAGE_INPUT_KINDS)
}

export interface DeepChatTapeToolIdentity {
Expand Down Expand Up @@ -130,7 +162,7 @@ export function tapeMessageRank(record: ChatMessageRecord, includePending: boole
}

export function readTapeMessageRetractionId(row: DeepChatTapeEntryRow): string | null {
if (row.kind !== 'event' || row.name !== 'message/retracted') {
if (row.kind !== 'event' || row.name !== TAPE_MESSAGE_RETRACTED_EVENT_NAME) {
return null
}

Expand All @@ -144,38 +176,46 @@ export function readTapeToolStatus(row: DeepChatTapeEntryRow): string | null {
return typeof meta.status === 'string' ? meta.status : null
}

export function tapeToolRank(row: DeepChatTapeEntryRow, includePending: boolean): number {
const status = readTapeToolStatus(row)
export function tapeToolRankFromStatus(status: string | null, includePending: boolean): number {
if (status === 'pending') {
return includePending ? 1 : 0
}
return status !== null && TERMINAL_TAPE_TOOL_STATUSES.has(status) ? 2 : 0
}

export function readTapeToolIdentity(row: DeepChatTapeEntryRow): DeepChatTapeToolIdentity | null {
if (row.kind !== 'tool_call' && row.kind !== 'tool_result') {
export function tapeToolRank(row: DeepChatTapeEntryRow, includePending: boolean): number {
return tapeToolRankFromStatus(readTapeToolStatus(row), includePending)
}

/** `readTapeToolIdentity` for a caller that has already parsed `payload_json`. */
export function readTapeToolIdentityFromPayload(
kind: DeepChatTapeEntryRow['kind'],
payload: Record<string, unknown>
): DeepChatTapeToolIdentity | null {
if (kind !== 'tool_call' && kind !== 'tool_result') {
return null
}

const payload = parseTapeJsonObject(row.payload_json)
const messageId = payload.messageId
if (typeof messageId !== 'string' || messageId.length === 0) {
return null
}

let toolCallId: unknown
if (row.kind === 'tool_call') {
toolCallId = parseNestedTapeJsonObject(payload.toolCall).id
} else {
toolCallId = payload.toolCallId
}

const toolCallId =
kind === 'tool_call' ? parseNestedTapeJsonObject(payload.toolCall).id : payload.toolCallId
if (typeof toolCallId !== 'string' || toolCallId.length === 0) {
return null
}

return {
key: `${row.kind}:${messageId}:${toolCallId}`,
key: `${kind}:${messageId}:${toolCallId}`,
messageId
}
}

export function readTapeToolIdentity(row: DeepChatTapeEntryRow): DeepChatTapeToolIdentity | null {
if (row.kind !== 'tool_call' && row.kind !== 'tool_result') {
return null
}
return readTapeToolIdentityFromPayload(row.kind, parseTapeJsonObject(row.payload_json))
}
75 changes: 45 additions & 30 deletions src/main/tape/domain/effectiveView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import { CONTRACT_TAPE_EVENT_NAMES, isContractTapeReservedName } from './contrac
import { TOOL_SURFACE_TAPE_EVENT_NAMES } from './toolSurfaceFacts'
import { TAPE_VIEW_MANIFEST_EVENT_NAME } from './viewManifest'
import {
TAPE_MESSAGE_RETRACTED_EVENT_NAME,
parseNestedTapeJsonObject,
parseTapeJsonObject,
readTapeMessageRetractionId,
readTapeToolIdentity,
readTapeToolIdentityFromPayload,
readTapeToolStatus,
tapeEntryToMessageRecord,
tapeMessageRank,
tapeToolRank
tapeToolRankFromStatus
} from './effectiveSemantics'
import {
parseTapeProviderAttemptEvent,
Expand Down Expand Up @@ -43,7 +45,7 @@ interface EffectiveTapeViewOptions {
}

export const DEFAULT_EXCLUDED_TAPE_EVENT_NAMES = [
'message/retracted',
TAPE_MESSAGE_RETRACTED_EVENT_NAME,
'message/compaction_indicator',
'migration/backfill',
TAPE_COMPACTION_MODEL_CALL_EVENT_NAME,
Expand All @@ -60,18 +62,25 @@ type EffectiveMessageCandidate = {
record: ChatMessageRecord
}

/** A tool row with everything the fold needs already parsed, so no JSON is parsed twice. */
type EffectiveToolCandidate = {
row: DeepChatTapeEntryRow
messageId: string
rank: number
/** `payload.orderSeq` as stored; when it already matches the message, projection is a no-op. */
storedOrderSeq: unknown
}

export function projectTapeToolOrderSeq(
row: DeepChatTapeEntryRow,
effectiveMessageOrderSeqById: ReadonlyMap<string, number>
): DeepChatTapeEntryRow {
const identity = readTapeToolIdentity(row)
const payload = parseTapeJsonObject(row.payload_json)
const identity = readTapeToolIdentityFromPayload(row.kind, payload)
if (!identity) return row

const effectiveOrderSeq = effectiveMessageOrderSeqById.get(identity.messageId)
if (effectiveOrderSeq === undefined) return row

const payload = parseTapeJsonObject(row.payload_json)
if (payload.orderSeq === effectiveOrderSeq) return row
if (effectiveOrderSeq === undefined || payload.orderSeq === effectiveOrderSeq) return row
return {
...row,
payload_json: JSON.stringify({ ...payload, orderSeq: effectiveOrderSeq })
Expand Down Expand Up @@ -132,23 +141,16 @@ function isAuditEvent(row: DeepChatTapeEntryRow): boolean {
}

function shouldReplaceToolRow(
current: DeepChatTapeEntryRow | undefined,
next: DeepChatTapeEntryRow,
includePending: boolean
current: EffectiveToolCandidate | undefined,
next: EffectiveToolCandidate
): boolean {
if (!current) {
return true
}

const currentRank = tapeToolRank(current, includePending)
const nextRank = tapeToolRank(next, includePending)
if (nextRank > currentRank) {
return true
}
if (nextRank < currentRank) {
return false
if (next.rank !== current.rank) {
return next.rank > current.rank
}
return next.entry_id > current.entry_id
return next.row.entry_id > current.row.entry_id
}

function matchesKinds(
Expand Down Expand Up @@ -184,7 +186,7 @@ export function buildEffectiveTapeView(
const includeAuditEvents = options.includeAuditEvents === true
const messageCandidates = new Map<string, EffectiveMessageCandidate>()
const retractedMessageIds = new Set<string>()
const toolRows = new Map<string, { row: DeepChatTapeEntryRow; messageId: string }>()
const toolCandidates = new Map<string, EffectiveToolCandidate>()
const anchorRows: DeepChatTapeEntryRow[] = []
const eventRows: DeepChatTapeEntryRow[] = []

Expand Down Expand Up @@ -227,13 +229,18 @@ export function buildEffectiveTapeView(
continue
}

const identity = readTapeToolIdentity(row)
if (!identity || tapeToolRank(row, includePending) === 0) {
const payload = parseTapeJsonObject(row.payload_json)
const identity = readTapeToolIdentityFromPayload(row.kind, payload)
if (!identity) {
continue
}
const current = toolRows.get(identity.key)?.row
if (shouldReplaceToolRow(current, row, includePending)) {
toolRows.set(identity.key, { row, messageId: identity.messageId })
const rank = tapeToolRankFromStatus(readTapeToolStatus(row), includePending)
if (rank === 0) {
continue
}
const candidate = { row, messageId: identity.messageId, rank, storedOrderSeq: payload.orderSeq }
if (shouldReplaceToolRow(toolCandidates.get(identity.key), candidate)) {
toolCandidates.set(identity.key, candidate)
}
}

Expand All @@ -244,13 +251,21 @@ export function buildEffectiveTapeView(
left.record.orderSeq - right.record.orderSeq ||
compareSqliteBinaryText(left.record.id, right.record.id)
)
const effectiveMessageIds = new Set(messageRows.map((candidate) => candidate.record.id))
const effectiveMessageOrderSeqById = new Map(
messageRows.map((candidate) => [candidate.record.id, candidate.record.orderSeq])
)
const effectiveToolRows = [...toolRows.values()]
.filter((candidate) => effectiveMessageIds.has(candidate.messageId))
.map((candidate) => projectTapeToolOrderSeq(candidate.row, effectiveMessageOrderSeqById))
// Tool rows only survive when their message did; the stored orderSeq matches unless a
// compaction shift moved the message, so the re-serialising projection is the rare path.
const effectiveToolRows: DeepChatTapeEntryRow[] = []
for (const candidate of toolCandidates.values()) {
const effectiveOrderSeq = effectiveMessageOrderSeqById.get(candidate.messageId)
if (effectiveOrderSeq === undefined) continue
effectiveToolRows.push(
candidate.storedOrderSeq === effectiveOrderSeq
? candidate.row
: projectTapeToolOrderSeq(candidate.row, effectiveMessageOrderSeqById)
)
}
const effectiveRows = [
...anchorRows,
...eventRows,
Expand Down
Loading