Resources
Soul memory under Phase 2 is the (kind=KIND_MEMORY, name="default", version_index=N) column of the unified SoulContent object. Each entry is a Seal-encrypted blob on Walrus, with an immutable on-chain pointer and a strict append + soft-delete + hard-purge lifecycle.
Memory lives as ContentSlot rows under the SoulContent object. There is no separate SoulMemory shared object after Phase 2.
// All memory versions for a Soul:
items[ContentKey { kind: 1 /* KIND_MEMORY */, name: "default" }]
→ vector<ContentSlot>
// One entry:
ContentSlot {
version: u64,
kind: 1,
blob_object_id: ID, // Walrus Blob
is_public: false, // memory is never public
deleted: bool,
purged: bool,
download_policy: 1, // OWNER_ONLY
grant_scope_mask: 2, // SCOPE_MEMORY
read_mode_mask: OWNER | GRANT,
op_mask: APPEND | DELETE | PURGE,
seal_encrypted: true,
created_at_ms: u64,
}The canonical name for memory is "default"; the on-chain assertion content::assert_canonical_name_for_kind rejects any other name for KIND_MEMORY. The version index is the 0-based vector index — the first founding memory is (1, "default", 0).
| Caller | Entry | Notes |
|---|---|---|
| creator at mint | market::mint_* | Optional founding memory is appended as (1, "default", 0) in the same PTB. |
| owner | content::append_version_as_owner | Pushes a new version. version_index is auto-incremented. |
| granted agent | content::append_version_as_granted_agent | Requires SoulGrant with SCOPE_MEMORY (bit 2) covering this Soul. |
When the owner appends a new memory version, the web app issues scope-matched grants to every active agent that doesn't already cover SCOPE_MEMORY. Existing scopes are preserved via the grant-merge-masks pre-check; supersede is the on-chain mechanism. Failures surface as a yellow banner with retry — see Agent Integration.
Memory slots always have read_mode_mask = OWNER | GRANT and never include READ_PUBLIC or READ_PAID. There is no public-memory mode. Every entry is Seal-bound to the canonical Phase 2 document ID:
"soul-content:" + version_byte(1) + kind_be(4 = 0x00000001)
+ content_object_id(32) + "default" + 0x00
+ version_index_be(8) + nonce(16)See Walrus & Seal for the universal doc-id format and approval entries.
content::delete_version_as_owner or delete_version_as_granted_agent) flips deleted = true on the slot. The Walrus blob remains; reads abort with EVersionDeleted. version_index is preserved.content::purge_deleted_version_as_owner) is owner-only and only valid after soft delete. It clears the on-chain blob pointer entirely and emits ContentVersionPurged.After a successful TX, the API mirrors a SoulContentVersionRecord row keyed by (soulId, contentId, kind, name, versionIndex) with these key fields:
contentOnChainId — the SoulContent shared-object IDkind — 1 for memoryname — "default"versionIndex — bigint / decimal stringwriterAddress — address that signed the append TXwriterKind — "creator" | "owner" | "granted-agent"blobObjectId — Sui object ID of the Walrus BlobblobId — Walrus blob ID (used to build the download URL)sealSidecar — encrypted DEK envelope for client-side decryptiondeletedAt / purgedAt — set by mirror writes on the respective TXsMemory entries are looked up by the triple (kind, name, versionIndex). There is no legacy timestampKey addressing post-phase 2.
GET /api/souls/[id]/content/1/default/N/access
// Owner / granted-agent response (memory has no public or paid path)
{
artifact: { walrusBlobUrl, walrusBlobId, blobObjectId },
accessPolicy: {
packageId,
stateObjectId,
contentObjectId,
kind: 1,
name: "default",
versionIndex: N,
moduleName: "content",
functionName:
"seal_approve_content_owner"
| "seal_approve_content_granted_agent",
soulGrantObjectId: string | null,
documentIdHex: string,
},
seal: { network, threshold, serverConfigs, verifyKeyServers },
sealSidecar: { encryptedDek, iv, cipher, fileName, mimeType, contentHash },
viewerAddress: string,
accessKind: "owner" | "granted-agent",
sessionTtlMin: number,
}