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.
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.