Reading items and history

Everything in a complete repository is read through three commands: synck item list, synck item show and synck item export. They never change the repository, need no author or account, and never contact the service that produced it. The examples use the sample repository from the tutorial.

Pointing at a repository

Every item command takes --path to name the repository directory. Without it, the current directory is used:

$ synck item list --path launch-project
$ cd launch-project && synck item list

The two commands are equivalent. The item commands always answer in JSON, designed for scripts and other programs. The examples here use jq to pick out fields, but any JSON tool will do.

Listing items

With no other options, item list shows the top level of the repository:

$ synck item list --path launch-project | jq -r '.items[] | "\(.item_type)\t\(.name)\t\(.item_id)"'
CHAT	chat-01a174b7-8a7d-74e0-b0d7-81d946ca6792	01a174b7-8a7d-74e0-b0d7-81d946ca6792
FOLDER	Launch	01a174b7-8a7d-74e0-b0d7-81cf3866d6b9

The top level contains the files and folders that have no parent folder, and also every non-tree item: chats, posts, calls, tasks, calendars and so on. Non-tree items never appear inside a folder listing. To list a folder’s contents, pass its ID with --parent:

$ synck item list --parent 01a174b7-8a7d-74e0-b0d7-81cf3866d6b9 --path launch-project | jq -r '.items[] | "\(.item_type)\t\(.name)"'
FILE	budget.csv
FILE	proposal.txt

Each entry in items describes one live item, meaning it is not in the trash. The fields you will use most are:

Field Meaning
item_id The permanent ID you pass to other commands
item_type FILE, FOLDER, CHAT, EMAIL, …
name The item’s current name. For non-tree items such as chats, this is an internal identifier; the title people see is in item show
parent_folder_id The containing folder’s ID, or null at the top level
head_seq The position of the item’s latest event in its own history
created_by, created_at Who created it, and when
sequencing_mode LOCAL for most items. Others are sequenced by an external authority; see taking over

Items are listed in name order. item list shows what is live now. It is not a list of everything that ever existed. Earlier states are covered below.

Paging through large folders

A listing returns at most 100 items by default. --limit changes the page size (1 to 512). When there are more items, next_cursor is not null. Pass it back, exactly as returned, to --after to get the next page:

$ synck item list --limit 1 --path launch-project | jq -c '{names: [.items[].name], next_cursor}'
{"names":["chat-01a174b7-8a7d-74e0-b0d7-81d946ca6792"],"next_cursor":{"repo_id":"01a174b7-8a7d-74e0-b0d7-81b1dcfa6ccd","parent_item_id":null,"through_repo_seq":null,"name_key":"chat-01a174b7-8a7d-74e0-b0d7-81d946ca6792","item_id":"01a174b7-8a7d-74e0-b0d7-81d946ca6792"}}
$ CURSOR=$(synck item list --limit 1 --path launch-project | jq -c '.next_cursor')
$ synck item list --limit 1 --after "$CURSOR" --path launch-project | jq -c '{names: [.items[].name], next_cursor}'
{"names":["Launch"],"next_cursor":null}

Keep the same --parent and --through for every page of one listing. Pages of the current state are separate reads. If someone writes to the repository between them, a later page reflects that write. When you need a listing that cannot change underneath you, page through a fixed checkpoint with --through.

Reading an item

synck item show ITEM_ID rebuilds one item from its history and returns it:

$ synck item show 01a174b7-8f4e-76f1-8ac4-440db922fb52 --path launch-project | jq '.item.projection | {name, size, content_item_seq, last_updated_by, last_updated_at}'
{
  "name": "proposal.txt",
  "size": 110,
  "content_item_seq": 3,
  "last_updated_by": "Maria",
  "last_updated_at": "2026-10-25T20:19:09.605371797Z"
}

The result has the same overall shape for every kind of item:

Part What it holds
item.projection Fields every item has: type, name, parent, lifecycle, current content hash and size for files, and who last changed it
item.subitems The item’s inner records, such as chat messages, comments, reactions, attachments, task entries and calendar events, each with its author, time and content
item.chat, item.post, item.native, … Type-specific state, such as a chat’s title and participants
refs The item’s attached ledger, such as a file’s named versions
item.presentation_warnings Anything the reader could not present. Empty when everything was understood

Synck’s shared readers understand the item types SynckHub stores: files and their versions, chats, email, posts, task lists, records, tasks, calendars, calls and time tracking. They turn stored events into the readable structure above.

Conversations

A chat’s title and kind are in item.chat:

$ synck item show 01a174b7-8a7d-74e0-b0d7-81d946ca6792 --path launch-project | jq -r '.item.chat | "\(.title) (\(.profile.chat_kind))"'
Launch review (ROOM)

SynckHub’s chat kinds are ROOM for team chats, DIRECT for direct messages between two people, and ASSISTANT_DIRECT for a conversation with an AI assistant. Its messages are the chat’s subitems, oldest first:

$ synck item show 01a174b7-8a7d-74e0-b0d7-81d946ca6792 --path launch-project | jq -r '.item.subitems[] | "\(.created_at)  \(.sender_id): \(.body)"'
2026-10-25T20:19:09.532710906Z  Sam: Please check the budget before Friday.
2026-10-25T20:19:09.624660131Z  Maria: The revised figures are ready. Fit-out was missing.
2026-10-25T20:19:09.667950373Z  Sam: Approved. Send it to the client.

Each message also records its lifecycle (live or deleted) and the message it replies to, if any. Reactions and attachments are subitems of their own, linked to their message through parent_item_seq.

Workspaces

A SynckHub workspace (a case, matter, deal or project) is a folder whose item_subtype is a case subtype, STANDARD_CASE by default:

$ synck item show 01a174b7-8a7d-74e0-b0d7-81cf3866d6b9 --path launch-project | jq -c '{name: .item.projection.name, item_subtype: .item.projection.item_subtype, warnings: .item.presentation_warnings}'
{"name":"Launch","item_subtype":"STANDARD_CASE","warnings":["Supplemental workflow/revision annotations are retained without application presentation or execution."]}

Its workspace state, such as whether it is open or closed and its workflow stage, is recorded as FOLDER::WORKFLOW_RUNTIME events in its history. Synck preserves and verifies those events, and lists them under item.ancillary_events. Running workflows is SynckHub’s job, so Synck keeps these events as data and does not act on them; the warning says exactly that. The workspace’s files are its ordinary folder contents.

The chats, tasks and other non-tree records that SynckHub associates with a workspace are not inside the folder. They are listed at the top level, like every non-tree item.

Email

Saved email is read the same way: item show on an email item returns its headers, references to its stored body and original message, and one EMAIL::ATTACHMENT subitem per attachment. When the original .eml or .msg file was kept, synck item export writes it back out exactly as it was saved. Synck preserves stored mail. It does not send or receive mail or connect to a mailbox.

Attachments

Attachments appear as references inside an item, each with a name, a size and the hash of its content. synck item export extracts FILE items and the original files of email items. It is not a general attachment exporter. Programs that embed the Synck library can read attachment content through its verified content readers.

When the reader cannot present something

If a repository contains an event or item type that this version of Synck does not know how to present, item show says so in presentation_warnings. The underlying history is still preserved exactly, and it is still verified and copied. An unknown record is never dropped.

Extracting files

synck item export FILE_ID DESTINATION writes a file’s content to a new file:

$ synck item export 01a174b7-8f4e-76f1-8ac4-440db922fb52 proposal.txt --path launch-project
{"bytes":110,"destination":"proposal.txt","item_id":"01a174b7-8f4e-76f1-8ac4-440db922fb52","schema_version":1}

Synck verifies the content’s size and SHA-256 before writing. The destination must not exist, and its folder must. Existing files are never overwritten. To extract every file in a folder, combine list and export:

synck item list --parent 01a174b7-8a7d-74e0-b0d7-81cf3866d6b9 --path launch-project |
  jq -r '.items[] | select(.item_type=="FILE") | "\(.item_id) \(.name)"' |
  while read -r id name; do
    synck item export "$id" "out/$name" --path launch-project
  done

To get ordinary editable files for a whole repository, a checkout is usually more convenient.

Reading the past

Every item command accepts --through N to read the repository as it was at the end of position N in its history:

$ synck item export 01a174b7-8f4e-76f1-8ac4-440db922fb52 proposal-first-draft.txt --through 5 --path launch-project
{"bytes":91,"destination":"proposal-first-draft.txt","item_id":"01a174b7-8f4e-76f1-8ac4-440db922fb52","schema_version":1}
$ synck item show 01a174b7-8a7d-74e0-b0d7-81d946ca6792 --through 5 --path launch-project | jq -r '.item.subitems[] | "\(.sender_id): \(.body)"'
Sam: Please check the budget before Friday.
$ synck item list --through 2 --path launch-project | jq -r '.items[] | "\(.item_type) \(.name) (\(.head_seq) events)"'
CHAT chat-01a174b7-8a7d-74e0-b0d7-81d946ca6792 (2 events)
FOLDER Launch (4 events)

N is a repository sequence, the position of a commit in the repository’s overall history. It is not a file’s item sequence and not a commit ID. The position must be a sealed checkpoint, meaning one that the history has fully reached:

$ synck item list --through 99 --path launch-project
synck: historical sequence is not a sealed repository checkpoint

synck repository verify reports the latest checkpoint as terminal_repo_seq. Historical listings are fixed: paging through a checkpoint always returns the same items, however the repository changes afterwards.

Finding the right checkpoint

synck repository log lists the repository’s commits, newest first, with the events in each one:

$ synck repository log launch-project --limit 3 | jq -r '.commits[] | "\(.repo_seq)  \(.author)  \(.created_at)  \([.events[].event_type] | join(", "))"'
8  Sam  2026-10-25T20:19:09.667950373Z  CHAT::MESSAGE::CREATE
7  Maria  2026-10-25T20:19:09.624660131Z  CHAT::MESSAGE::CREATE
6  Maria  2026-10-25T20:19:09.605371797Z  FILE::SET_CONTENT

Each commit has its repo_seq, which is the number to pass to --through, plus its commit_id, author, committer, time and message. Each event names its item_id, its position (seq) in that item’s history, its type and its ledger: ITEM for the item’s own history, REF for its ref ledger. To find when the proposal changed, look for FILE::SET_CONTENT events with the proposal’s ID.

A page holds 20 commits unless you set --limit (1 to 512). For older commits, pass the page’s next_before back as --before:

$ synck repository log launch-project --limit 3 | jq .next_before
6
$ synck repository log launch-project --limit 3 --before 6 | jq -r '.commits[] | "\(.repo_seq)  \(.author)  \([.events[].event_type] | join(", "))"'
5  Maria  FILE::CREATE, FILE::SET_CONTENT
4  Sam  CHAT::MESSAGE::CREATE
3  Maria  FILE::CREATE, FILE::SET_CONTENT

next_before is null on the page with the first commit.