mem.json is the contract. Check memVersion first, parse by major version, and ignore fields you do not know. The shapes below are the spec’s.
interface MemManifest {
memVersion: string; // "1.0": check the major version before parsing
tier: "portable" | "full"; // which projection this is
profile: "bundled" | "linked"; // where the bytes are
generator: string; // "memoresse-mem/0.1"
package: {
id: string;
title: string;
event?: string; date?: string; location?: string;
primaryThemes?: string[];
narrativeSummary?: string; // the arc of the whole memory, in prose
involvedRoles?: string[]; // portable: roles, never names
involvedPeople?: string[]; // full only: persona ids
roleGraph?: MemRoleGraphEntry[];
declaredEmotion?: DeclaredEmotion; // what the owner said they felt
};
assets: MemManifestAsset[];
segments?: MemSegment[]; // ordered sub-events
provenance: {
algorithm: "sha256";
manifestHash: string;
assetCount: number;
anchor?: MemAnchor; // the OpenTimestamps proof, folded in
};
retrieval?: { instructions: string; manifestUrl?: string }; // linked profile only
presentation?: MemPresentation; // how to play it: pace, why, slideOrder, sections, soundtrack
song?: unknown; // the generated-soundtrack recipe
}The package: the memory as a whole
title, event, date, location and primaryThemes say what this is. narrativeSummary is the arc of the whole memory in a paragraph. The people appear as involvedRoles with a roleGraph (each role, its closeness band, its coarse relations). declaredEmotion is what the owner said they felt. From this block alone a reader can introduce the memory.
Each asset: one moment
interface MemManifestAsset {
id: string;
kind: "photo" | "video" | "voice" | "note";
caption?: string;
description?: string; // sentence-level prose: the user's story, else the caption
themes?: string[];
emotionalWeight?: number; // 0..1, the value only
sensitivity?: number; // 0..1
takenAt?: string; // ISO 8601
focalPoint?: FocalPoint; // { x, y, zoom }: where to look
focalPointEnd?: FocalPoint; // optional: where a pan ends
transcript?: string; // voice assets: travels inline
associatedAssets?: string[]; // a voice note -> its photo
videoUrl?: string; startSec?: number; endSec?: number; // a clip: the kept window
contentHash: string; // sha256 hex: the asset's identity
locator?: MemLocator; // { kind: "embedded" | "url" | "lockbox", ref, bytes?, mediaType? }
involvedRoles?: string[]; audioSpeakerRole?: string; // portable
involvedPeople?: string[]; audioSpeaker?: string; // full only
reactions?: Record<string, string[]>; hearts?: string[]; // full only
}description is sentence-level prose and the highest-value field for anything that writes or narrates. emotionalWeight and sensitivity are values in 0..1; trust them as given, since the file does not carry what produced them. focalPoint says where to look and focalPointEnd turns that into a pan. A voice or note asset carries its transcript inline. A video clip carries videoUrl with startSec and endSec for the kept window. contentHash is the asset’s identity; locator is one place to fetch its bytes.
Segments and presentation: how it plays
segments is the sequence layer: ordered sub-events, each with a label, a time range and its assetIds. presentation says how to play the file: pace, the wider thread, the one-sentence why, an explicit slideOrder, sections for a card view, the soundtrackAssetId and its soundtrackBpm, and the vibe the sender confirmed. These are declarative values: a player honors them to reproduce the intended experience, or ignores them and still plays. song is the recipe behind a generated track.
The rest
provenance is chapter 7. tier, profile and retrieval are chapter 8. What travels and what does not is chapter 5.