> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tokenfactory.nebius.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Tree stores

`SyncStore` and `AsyncStore` store immutable trees. Sessions, Docker builds, and
upload reuse share one Store. Helpers and direct
constructors accept it through `store=`. The Store holds references; images and files remain
on the ConTree server.

## Nodes and tree kinds

A `Node` contains `id`, `parent_id`, `kind`, and JSON-object `data`. Its generated
ID identifies a record, not a command, image, timestamp, or execution order.
Retain the same Node when retrying a write. Two executions can have equal inputs
and still produce different nodes.

A tree has one root and an immutable string `kind`. Standard consumers use
`session`, `docker-build`, `upload-cache`, and `file-sources`. Applications can
supply their own kinds. A node’s kind describes its step, such as `run`, `stage`,
or `config`. Tree kinds let a CLI exclude service histories before pagination.

**Sync**

```python theme={null}
from contree_sdk.store import Node, SyncMemoryStore

with SyncMemoryStore() as store:
    store.create_session("example", kind="session", ttl=3600)
    root = Node(parent_id=None, kind="init", data={"image_uuid": "base-image"})
    store.append("example", root)
    first = Node(parent_id=root.id, kind="run", data={"user": {"label": "first"}})
    second = Node(parent_id=root.id, kind="run", data={"user": {"label": "second"}})
    store.append("example", first)
    store.append("example", second)
    store.append("example", first)  # A repeated delivery creates no duplicate.
    assert store.get_nodes("example", (first.id, "missing")) == (first, None)
    assert len(store.list_children("example", root.id).items) == 2
    page = store.list_sessions(kind="session", limit=20)
    assert page.items[0].session_id == "example"
```

**Async**

```python theme={null}
from contree_sdk.store import AsyncMemoryStore, Node

async with AsyncMemoryStore() as store:
    await store.create_session("example", kind="session", ttl=3600)
    root = Node(parent_id=None, kind="init", data={"image_uuid": "base-image"})
    await store.append("example", root)
    first = Node(parent_id=root.id, kind="run", data={"user": {"label": "first"}})
    second = Node(parent_id=root.id, kind="run", data={"user": {"label": "second"}})
    await store.append("example", first)
    await store.append("example", second)
    await store.append("example", first)
    assert len((await store.list_children("example", root.id)).items) == 2
    page = await store.list_sessions(kind="session", limit=20)
    assert page.items[0].session_id == "example"
```

## Contract

* `create_session()` creates an empty tree. Repeating it with the same kind returns
  the existing tree without renewing its TTL. A different kind raises `ValueError`.
* `append()` requires a live tree and a parent in that tree, except for its root.
  It atomically inserts one node. Equal identity and contents succeed on retry;
  changed contents or a second root raise `NodeConflictError` without overwriting.
* Reads return independent JSON values. Mutating a returned dictionary does not
  modify storage. Updating a saved node requires a new node.
* Calls are atomic across clients sharing a backend. A successful write is
  visible to subsequent reads from any client, unless deletion or expiry removes
  the tree. Concurrent calls may take either order. Separate calls are not a
  transaction. A lost response leaves the write outcome unknown; retry an append
  with the same prepared Node.
* `get_session()` returns `None` for a missing or expired tree. Reading a missing
  node or appending to a missing tree raises `ValueError`.
* `get_nodes(session_id, node_ids)` reads up to 1000 identities in one atomic
  call. The returned tuple preserves input order and duplicates. Missing nodes
  occupy `None` slots. A missing or expired tree raises `ValueError`, including
  for an empty batch. Each returned node contains independent JSON values.
* `list_sessions()`, `list_nodes()`, and `list_children()` filter before pagination
  and order by identity. `list_history()` follows parents from the specified node.
  Pass `next_cursor` with the same selection; `None` means the end. Pages are live
  reads and do not share a snapshot across calls.
* List methods request 1000 items by default. A backend can return fewer items
  with a continuation cursor to bound response size. Always follow `next_cursor`;
  a short page does not imply the end. Treat cursors as opaque positions. They
  are not authorization tokens; a remote backend must check access separately.
* TTL belongs to the whole tree. `set_session_ttl()` sets a new expiry from now.
  Reads and append do not renew it. `ttl=None` uses the implementation policy;
  the supplied Memory and SQLite implementations retain the tree until deletion.
  Zero expires immediately; negative or nonfinite TTL raises `ValueError`.
* `delete_session()` removes the tree and returns whether stored data existed,
  including expired data. It does not delete remote images or cancel operations.
  A late append cannot recreate a deleted tree. Explicit creation can reuse its
  name; use a new session ID when a new lifetime must remain distinguishable.

## Execution belongs to Session

The Store has no selected branch or shared head. Each Session keeps its own
position and local branch names. Two actors executing from the same parent add
two children. A late result keeps its captured parent even after local navigation.
Use separate Session instances for independent actors. Serialize navigation when
sharing one Session across threads. See [Branch, switch, and roll back](https://docs.tokenfactory.nebius.com/sandboxes/sdk/python_sdk/branching.md) and [Save and resume a session](https://docs.tokenfactory.nebius.com/sandboxes/sdk/python_sdk/sessions.md)
for selection and explicit resume.

For a live tree with root `R`, actors can append `R → A` and `R → B` in either
order. Both nodes remain available. Each actor can then extend its own node or
explicitly select a node written by another actor. Repeating the same append
adds no duplicate. Changing a node’s parent or data under the same ID raises
`NodeConflictError`. This holds for any number of actors because each successful
append adds one node without changing existing nodes.

Each actor must retain its selected `(session_id, node_id)` and the prepared
Node for a retry. Use explicit `node_id` when opening a shared session;
automatic selection is not a durable actor position. Store does not merge
filesystem changes or make VM execution exactly once.

An identity cursor is not an event subscription. Concurrent inserts with IDs
before that cursor will not appear in the remaining pages. Restart enumeration
to discover them, or exchange explicit node IDs between actors. The ancestry of
a known node is stable while other actors append, so `list_history()` can page
that path without mixing branches. Deletion and expiry can still interrupt it.

Pending operations and their completions are immutable nodes too. A completion
node contains the response and retained image, so atomic append also protects
completion from partial writes. See [Advanced: resume a background operation](https://docs.tokenfactory.nebius.com/sandboxes/sdk/python_sdk/detached-operations.md).

Session lists operation registrations by page and reads their completion nodes
with `get_nodes()`. It does not fetch each registration again. Successful image
commits record a `staging_complete` marker in the SDK payload. Staging recovery
stops there because that image contains all staged inputs captured for the
operation. Older records remain readable through ancestry traversal. Failed or
discarded operations do not consume their source staging.

Docker searches `run` children of the selected parent and `use` nodes for FROM.
It still checks Docker hashes in the client. Large sibling sets and accumulated
FROM records can require multiple pages. Opening a session without `node_id`
still enumerates its tree to detect ambiguous continuation; pass `node_id` when
resuming a known position.

Custom backends implement this contract through `SyncStore` or `AsyncStore`.
Atomicity must cover all clients, including separate processes and connections.
An SDK-side lock alone does not meet that requirement. Memory uses SQLite’s
in-memory engine; SQLite stores persist to a file. `AsyncSQLiteStore` uses
`aiosqlite`. Existing databases from earlier experimental contracts are not
migrated into these trees.

## `class AsyncMemoryStore`

```python theme={null}
class contree_sdk.store.AsyncMemoryStore(*, clock)
```

### Methods

#### `append()`

```python theme={null}
async def append(session_id, node) -> None
```

**session\_id** *required*

**node** *required*

**returns** `None`

#### `close()`

```python theme={null}
async def close() -> None
```

**returns** `None`

#### `create_session()`

```python theme={null}
async def create_session(session_id, *, kind, ttl) -> SessionInfo
```

**session\_id** *required*

**kind** (default: 'session')

**ttl** (default: None)

**returns** `SessionInfo`

[`SessionInfo`](./store#contree_sdk.store.SessionInfo)

#### `delete_session()`

```python theme={null}
async def delete_session(session_id) -> bool
```

**session\_id** *required*

**returns** `bool`

#### `get_node()`

```python theme={null}
async def get_node(session_id, node_id) -> Node
```

**session\_id** *required*

**node\_id** *required*

**returns** `Node`

[`Node`](./store#contree_sdk.store.Node)

#### `get_nodes()`

```python theme={null}
async def get_nodes(session_id, node_ids) -> tuple[Node | None, ...]
```

Read up to 1000 identities atomically, preserving input order and duplicates.

Missing nodes occupy None slots. A missing or expired tree raises ValueError,
including for an empty batch. Returned nodes contain independent JSON.

**session\_id** *required*

**node\_ids** *required*

**returns** `tuple[Node | None, ...]`

tuple\[[`Node`](./store#contree_sdk.store.Node) | None, ...]

#### `get_session()`

```python theme={null}
async def get_session(session_id) -> SessionInfo | None
```

**session\_id** *required*

**returns** `SessionInfo | None`

[`SessionInfo`](./store#contree_sdk.store.SessionInfo) | None

#### `list_children()`

```python theme={null}
async def list_children(session_id, parent_id, *, kind, limit, cursor) -> Page[Node]
```

**session\_id** *required*

**parent\_id** *required*

**kind** (default: None)

**limit** (default: 1000)

**cursor** (default: None)

**returns** `Page[Node]`

[`Page`](./store#contree_sdk.store.Page)\[[`Node`](./store#contree_sdk.store.Node)]

#### `list_history()`

```python theme={null}
async def list_history(session_id, from_node, *, limit, cursor) -> Page[Node]
```

**session\_id** *required*

**from\_node** *required*

**limit** (default: 1000)

**cursor** (default: None)

**returns** `Page[Node]`

[`Page`](./store#contree_sdk.store.Page)\[[`Node`](./store#contree_sdk.store.Node)]

#### `list_nodes()`

```python theme={null}
async def list_nodes(session_id, *, kind, limit, cursor) -> Page[Node]
```

**session\_id** *required*

**kind** (default: None)

**limit** (default: 1000)

**cursor** (default: None)

**returns** `Page[Node]`

[`Page`](./store#contree_sdk.store.Page)\[[`Node`](./store#contree_sdk.store.Node)]

#### `list_sessions()`

```python theme={null}
async def list_sessions(*, kind, prefix, limit, cursor) -> Page[SessionInfo]
```

**kind** (default: None)

**prefix** (default: '')

**limit** (default: 1000)

**cursor** (default: None)

**returns** `Page[SessionInfo]`

[`Page`](./store#contree_sdk.store.Page)\[[`SessionInfo`](./store#contree_sdk.store.SessionInfo)]

#### `set_session_ttl()`

```python theme={null}
async def set_session_ttl(session_id, ttl) -> SessionInfo
```

**session\_id** *required*

**ttl** *required*

**returns** `SessionInfo`

[`SessionInfo`](./store#contree_sdk.store.SessionInfo)

## `class AsyncSQLiteStore`

```python theme={null}
class contree_sdk.store.AsyncSQLiteStore(db_path, *, clock)
```

Native aiosqlite implementation of the same tree contract.

### Methods

#### `append()`

```python theme={null}
async def append(session_id, node) -> None
```

**session\_id** *required*

**node** *required*

**returns** `None`

#### `close()`

```python theme={null}
async def close() -> None
```

**returns** `None`

#### `create_session()`

```python theme={null}
async def create_session(session_id, *, kind, ttl) -> SessionInfo
```

**session\_id** *required*

**kind** (default: 'session')

**ttl** (default: None)

**returns** `SessionInfo`

[`SessionInfo`](./store#contree_sdk.store.SessionInfo)

#### `delete_session()`

```python theme={null}
async def delete_session(session_id) -> bool
```

**session\_id** *required*

**returns** `bool`

#### `get_node()`

```python theme={null}
async def get_node(session_id, node_id) -> Node
```

**session\_id** *required*

**node\_id** *required*

**returns** `Node`

[`Node`](./store#contree_sdk.store.Node)

#### `get_nodes()`

```python theme={null}
async def get_nodes(session_id, node_ids) -> tuple[Node | None, ...]
```

Read up to 1000 identities atomically, preserving input order and duplicates.

Missing nodes occupy None slots. A missing or expired tree raises ValueError,
including for an empty batch. Returned nodes contain independent JSON.

**session\_id** *required*

**node\_ids** *required*

**returns** `tuple[Node | None, ...]`

tuple\[[`Node`](./store#contree_sdk.store.Node) | None, ...]

#### `get_session()`

```python theme={null}
async def get_session(session_id) -> SessionInfo | None
```

**session\_id** *required*

**returns** `SessionInfo | None`

[`SessionInfo`](./store#contree_sdk.store.SessionInfo) | None

#### `list_children()`

```python theme={null}
async def list_children(session_id, parent_id, *, kind, limit, cursor) -> Page[Node]
```

**session\_id** *required*

**parent\_id** *required*

**kind** (default: None)

**limit** (default: 1000)

**cursor** (default: None)

**returns** `Page[Node]`

[`Page`](./store#contree_sdk.store.Page)\[[`Node`](./store#contree_sdk.store.Node)]

#### `list_history()`

```python theme={null}
async def list_history(session_id, from_node, *, limit, cursor) -> Page[Node]
```

**session\_id** *required*

**from\_node** *required*

**limit** (default: 1000)

**cursor** (default: None)

**returns** `Page[Node]`

[`Page`](./store#contree_sdk.store.Page)\[[`Node`](./store#contree_sdk.store.Node)]

#### `list_nodes()`

```python theme={null}
async def list_nodes(session_id, *, kind, limit, cursor) -> Page[Node]
```

**session\_id** *required*

**kind** (default: None)

**limit** (default: 1000)

**cursor** (default: None)

**returns** `Page[Node]`

[`Page`](./store#contree_sdk.store.Page)\[[`Node`](./store#contree_sdk.store.Node)]

#### `list_sessions()`

```python theme={null}
async def list_sessions(*, kind, prefix, limit, cursor) -> Page[SessionInfo]
```

**kind** (default: None)

**prefix** (default: '')

**limit** (default: 1000)

**cursor** (default: None)

**returns** `Page[SessionInfo]`

[`Page`](./store#contree_sdk.store.Page)\[[`SessionInfo`](./store#contree_sdk.store.SessionInfo)]

#### `set_session_ttl()`

```python theme={null}
async def set_session_ttl(session_id, ttl) -> SessionInfo
```

**session\_id** *required*

**ttl** *required*

**returns** `SessionInfo`

[`SessionInfo`](./store#contree_sdk.store.SessionInfo)

## `class AsyncStore`

```python theme={null}
class contree_sdk.store.AsyncStore()
```

Async equivalent of SyncStore with the same guarantees.

### Methods

#### `append()`

```python theme={null}
def append(session_id, node) -> None
```

**session\_id** *required*

**node** *required*

**returns** `None`

#### `close()`

```python theme={null}
def close() -> None
```

**returns** `None`

#### `create_session()`

```python theme={null}
def create_session(session_id, *, kind, ttl) -> SessionInfo
```

**session\_id** *required*

**kind** (default: 'session')

**ttl** (default: None)

**returns** `SessionInfo`

[`SessionInfo`](./store#contree_sdk.store.SessionInfo)

#### `delete_session()`

```python theme={null}
def delete_session(session_id) -> bool
```

**session\_id** *required*

**returns** `bool`

#### `get_node()`

```python theme={null}
def get_node(session_id, node_id) -> Node
```

**session\_id** *required*

**node\_id** *required*

**returns** `Node`

[`Node`](./store#contree_sdk.store.Node)

#### `get_nodes()`

```python theme={null}
def get_nodes(session_id, node_ids) -> tuple[Node | None, ...]
```

Read up to 1000 identities atomically, preserving input order and duplicates.

Missing nodes occupy None slots. A missing or expired tree raises ValueError,
including for an empty batch. Returned nodes contain independent JSON.

**session\_id** *required*

**node\_ids** *required*

**returns** `tuple[Node | None, ...]`

tuple\[[`Node`](./store#contree_sdk.store.Node) | None, ...]

#### `get_session()`

```python theme={null}
def get_session(session_id) -> SessionInfo | None
```

**session\_id** *required*

**returns** `SessionInfo | None`

[`SessionInfo`](./store#contree_sdk.store.SessionInfo) | None

#### `list_children()`

```python theme={null}
def list_children(session_id, parent_id, *, kind, limit, cursor) -> Page[Node]
```

**session\_id** *required*

**parent\_id** *required*

**kind** (default: None)

**limit** (default: 1000)

**cursor** (default: None)

**returns** `Page[Node]`

[`Page`](./store#contree_sdk.store.Page)\[[`Node`](./store#contree_sdk.store.Node)]

#### `list_history()`

```python theme={null}
def list_history(session_id, from_node, *, limit, cursor) -> Page[Node]
```

**session\_id** *required*

**from\_node** *required*

**limit** (default: 1000)

**cursor** (default: None)

**returns** `Page[Node]`

[`Page`](./store#contree_sdk.store.Page)\[[`Node`](./store#contree_sdk.store.Node)]

#### `list_nodes()`

```python theme={null}
def list_nodes(session_id, *, kind, limit, cursor) -> Page[Node]
```

**session\_id** *required*

**kind** (default: None)

**limit** (default: 1000)

**cursor** (default: None)

**returns** `Page[Node]`

[`Page`](./store#contree_sdk.store.Page)\[[`Node`](./store#contree_sdk.store.Node)]

#### `list_sessions()`

```python theme={null}
def list_sessions(*, kind, prefix, limit, cursor) -> Page[SessionInfo]
```

**kind** (default: None)

**prefix** (default: '')

**limit** (default: 1000)

**cursor** (default: None)

**returns** `Page[SessionInfo]`

[`Page`](./store#contree_sdk.store.Page)\[[`SessionInfo`](./store#contree_sdk.store.SessionInfo)]

#### `set_session_ttl()`

```python theme={null}
def set_session_ttl(session_id, ttl) -> SessionInfo
```

**session\_id** *required*

**ttl** *required*

**returns** `SessionInfo`

[`SessionInfo`](./store#contree_sdk.store.SessionInfo)

## `class Node`

```python theme={null}
class contree_sdk.store.Node(*, parent_id, kind, data, id)
```

### Attributes

**parent\_id** `str | None`

**kind** `str`

**data** `dict[str, str | int | float | bool | None | list[str | int | float | bool | None | list[JSONValue] | dict[str, JSONValue]] | dict[str, str | int | float | bool | None | list[JSONValue] | dict[str, JSONValue]]]`

**id** `str`

## `exception NodeConflictError`

```python theme={null}
exception contree_sdk.store.NodeConflictError()
```

An identity already names a different node, or the tree already has a root.

### Attributes

**args**

### Methods

#### `add_note()`

```python theme={null}
def add_note(note, /)
```

Add a note to the exception

#### `with_traceback()`

```python theme={null}
def with_traceback(tb, /)
```

Set self.**traceback** to tb and return self.

**NodeId**

alias of `str`

## `class Page`

```python theme={null}
class contree_sdk.store.Page(items, next_cursor)
```

### Attributes

**next\_cursor** `str | None`

**items** `tuple[TypeVar(T), ...]`

## `class SessionInfo`

```python theme={null}
class contree_sdk.store.SessionInfo(session_id, kind, created_at, expires_at)
```

### Attributes

**session\_id** `str`

**kind** `str`

**created\_at** `float`

**expires\_at** `float | None`

## `class SyncMemoryStore`

```python theme={null}
class contree_sdk.store.SyncMemoryStore(*, clock)
```

### Methods

#### `append()`

```python theme={null}
def append(session_id, node) -> None
```

**session\_id** *required*

**node** *required*

**returns** `None`

#### `close()`

```python theme={null}
def close() -> None
```

**returns** `None`

#### `create_session()`

```python theme={null}
def create_session(session_id, *, kind, ttl) -> SessionInfo
```

**session\_id** *required*

**kind** (default: 'session')

**ttl** (default: None)

**returns** `SessionInfo`

[`SessionInfo`](./store#contree_sdk.store.SessionInfo)

#### `delete_session()`

```python theme={null}
def delete_session(session_id) -> bool
```

**session\_id** *required*

**returns** `bool`

#### `get_node()`

```python theme={null}
def get_node(session_id, node_id) -> Node
```

**session\_id** *required*

**node\_id** *required*

**returns** `Node`

[`Node`](./store#contree_sdk.store.Node)

#### `get_nodes()`

```python theme={null}
def get_nodes(session_id, node_ids) -> tuple[Node | None, ...]
```

Read up to 1000 identities atomically, preserving input order and duplicates.

Missing nodes occupy None slots. A missing or expired tree raises ValueError,
including for an empty batch. Returned nodes contain independent JSON.

**session\_id** *required*

**node\_ids** *required*

**returns** `tuple[Node | None, ...]`

tuple\[[`Node`](./store#contree_sdk.store.Node) | None, ...]

#### `get_session()`

```python theme={null}
def get_session(session_id) -> SessionInfo | None
```

**session\_id** *required*

**returns** `SessionInfo | None`

[`SessionInfo`](./store#contree_sdk.store.SessionInfo) | None

#### `list_children()`

```python theme={null}
def list_children(session_id, parent_id, *, kind, limit, cursor) -> Page[Node]
```

**session\_id** *required*

**parent\_id** *required*

**kind** (default: None)

**limit** (default: 1000)

**cursor** (default: None)

**returns** `Page[Node]`

[`Page`](./store#contree_sdk.store.Page)\[[`Node`](./store#contree_sdk.store.Node)]

#### `list_history()`

```python theme={null}
def list_history(session_id, from_node, *, limit, cursor) -> Page[Node]
```

**session\_id** *required*

**from\_node** *required*

**limit** (default: 1000)

**cursor** (default: None)

**returns** `Page[Node]`

[`Page`](./store#contree_sdk.store.Page)\[[`Node`](./store#contree_sdk.store.Node)]

#### `list_nodes()`

```python theme={null}
def list_nodes(session_id, *, kind, limit, cursor) -> Page[Node]
```

**session\_id** *required*

**kind** (default: None)

**limit** (default: 1000)

**cursor** (default: None)

**returns** `Page[Node]`

[`Page`](./store#contree_sdk.store.Page)\[[`Node`](./store#contree_sdk.store.Node)]

#### `list_sessions()`

```python theme={null}
def list_sessions(*, kind, prefix, limit, cursor) -> Page[SessionInfo]
```

**kind** (default: None)

**prefix** (default: '')

**limit** (default: 1000)

**cursor** (default: None)

**returns** `Page[SessionInfo]`

[`Page`](./store#contree_sdk.store.Page)\[[`SessionInfo`](./store#contree_sdk.store.SessionInfo)]

#### `set_session_ttl()`

```python theme={null}
def set_session_ttl(session_id, ttl) -> SessionInfo
```

**session\_id** *required*

**ttl** *required*

**returns** `SessionInfo`

[`SessionInfo`](./store#contree_sdk.store.SessionInfo)

## `class SyncSQLiteStore`

```python theme={null}
class contree_sdk.store.SyncSQLiteStore(db_path, *, clock)
```

Persist trees. None TTL means no expiry for this implementation.

### Methods

#### `append()`

```python theme={null}
def append(session_id, node) -> None
```

**session\_id** *required*

**node** *required*

**returns** `None`

#### `close()`

```python theme={null}
def close() -> None
```

**returns** `None`

#### `create_session()`

```python theme={null}
def create_session(session_id, *, kind, ttl) -> SessionInfo
```

**session\_id** *required*

**kind** (default: 'session')

**ttl** (default: None)

**returns** `SessionInfo`

[`SessionInfo`](./store#contree_sdk.store.SessionInfo)

#### `delete_session()`

```python theme={null}
def delete_session(session_id) -> bool
```

**session\_id** *required*

**returns** `bool`

#### `get_node()`

```python theme={null}
def get_node(session_id, node_id) -> Node
```

**session\_id** *required*

**node\_id** *required*

**returns** `Node`

[`Node`](./store#contree_sdk.store.Node)

#### `get_nodes()`

```python theme={null}
def get_nodes(session_id, node_ids) -> tuple[Node | None, ...]
```

Read up to 1000 identities atomically, preserving input order and duplicates.

Missing nodes occupy None slots. A missing or expired tree raises ValueError,
including for an empty batch. Returned nodes contain independent JSON.

**session\_id** *required*

**node\_ids** *required*

**returns** `tuple[Node | None, ...]`

tuple\[[`Node`](./store#contree_sdk.store.Node) | None, ...]

#### `get_session()`

```python theme={null}
def get_session(session_id) -> SessionInfo | None
```

**session\_id** *required*

**returns** `SessionInfo | None`

[`SessionInfo`](./store#contree_sdk.store.SessionInfo) | None

#### `list_children()`

```python theme={null}
def list_children(session_id, parent_id, *, kind, limit, cursor) -> Page[Node]
```

**session\_id** *required*

**parent\_id** *required*

**kind** (default: None)

**limit** (default: 1000)

**cursor** (default: None)

**returns** `Page[Node]`

[`Page`](./store#contree_sdk.store.Page)\[[`Node`](./store#contree_sdk.store.Node)]

#### `list_history()`

```python theme={null}
def list_history(session_id, from_node, *, limit, cursor) -> Page[Node]
```

**session\_id** *required*

**from\_node** *required*

**limit** (default: 1000)

**cursor** (default: None)

**returns** `Page[Node]`

[`Page`](./store#contree_sdk.store.Page)\[[`Node`](./store#contree_sdk.store.Node)]

#### `list_nodes()`

```python theme={null}
def list_nodes(session_id, *, kind, limit, cursor) -> Page[Node]
```

**session\_id** *required*

**kind** (default: None)

**limit** (default: 1000)

**cursor** (default: None)

**returns** `Page[Node]`

[`Page`](./store#contree_sdk.store.Page)\[[`Node`](./store#contree_sdk.store.Node)]

#### `list_sessions()`

```python theme={null}
def list_sessions(*, kind, prefix, limit, cursor) -> Page[SessionInfo]
```

**kind** (default: None)

**prefix** (default: '')

**limit** (default: 1000)

**cursor** (default: None)

**returns** `Page[SessionInfo]`

[`Page`](./store#contree_sdk.store.Page)\[[`SessionInfo`](./store#contree_sdk.store.SessionInfo)]

#### `set_session_ttl()`

```python theme={null}
def set_session_ttl(session_id, ttl) -> SessionInfo
```

**session\_id** *required*

**ttl** *required*

**returns** `SessionInfo`

[`SessionInfo`](./store#contree_sdk.store.SessionInfo)

## `class SyncStore`

```python theme={null}
class contree_sdk.store.SyncStore()
```

Store independent trees with atomic, idempotent node insertion.

Repeating create\_session with the same kind returns the existing tree without
renewing TTL. append requires a live tree and an existing parent, except for
its single root. Repeating an identical node succeeds; changed data conflicts.
Reads return detached JSON. Node IDs are identities, not execution order.
Pages use stable identity order; they are live reads, not a shared snapshot.
None TTL delegates retention to the implementation.

Calls are atomic across clients sharing a backend. Once a write succeeds,
subsequent reads through any client observe it unless the tree was deleted
or expired. Concurrent calls may take either order; separate calls do not
form a transaction. A lost response does not imply that the write failed.
Retry append with the same node identity, parent, kind, and data.

### Methods

#### `append()`

```python theme={null}
@abstractmethod
def append(session_id, node) -> None
```

**session\_id** *required*

**node** *required*

**returns** `None`

#### `close()`

```python theme={null}
@abstractmethod
def close() -> None
```

**returns** `None`

#### `create_session()`

```python theme={null}
@abstractmethod
def create_session(session_id, *, kind, ttl) -> SessionInfo
```

**session\_id** *required*

**kind** (default: 'session')

**ttl** (default: None)

**returns** `SessionInfo`

[`SessionInfo`](./store#contree_sdk.store.SessionInfo)

#### `delete_session()`

```python theme={null}
@abstractmethod
def delete_session(session_id) -> bool
```

**session\_id** *required*

**returns** `bool`

#### `get_node()`

```python theme={null}
@abstractmethod
def get_node(session_id, node_id) -> Node
```

**session\_id** *required*

**node\_id** *required*

**returns** `Node`

[`Node`](./store#contree_sdk.store.Node)

#### `get_nodes()`

```python theme={null}
@abstractmethod
def get_nodes(session_id, node_ids) -> tuple[Node | None, ...]
```

Read up to 1000 identities atomically, preserving input order and duplicates.

Missing nodes occupy None slots. A missing or expired tree raises ValueError,
including for an empty batch. Returned nodes contain independent JSON.

**session\_id** *required*

**node\_ids** *required*

**returns** `tuple[Node | None, ...]`

tuple\[[`Node`](./store#contree_sdk.store.Node) | None, ...]

#### `get_session()`

```python theme={null}
@abstractmethod
def get_session(session_id) -> SessionInfo | None
```

**session\_id** *required*

**returns** `SessionInfo | None`

[`SessionInfo`](./store#contree_sdk.store.SessionInfo) | None

#### `list_children()`

```python theme={null}
@abstractmethod
def list_children(session_id, parent_id, *, kind, limit, cursor) -> Page[Node]
```

**session\_id** *required*

**parent\_id** *required*

**kind** (default: None)

**limit** (default: 1000)

**cursor** (default: None)

**returns** `Page[Node]`

[`Page`](./store#contree_sdk.store.Page)\[[`Node`](./store#contree_sdk.store.Node)]

#### `list_history()`

```python theme={null}
@abstractmethod
def list_history(session_id, from_node, *, limit, cursor) -> Page[Node]
```

**session\_id** *required*

**from\_node** *required*

**limit** (default: 1000)

**cursor** (default: None)

**returns** `Page[Node]`

[`Page`](./store#contree_sdk.store.Page)\[[`Node`](./store#contree_sdk.store.Node)]

#### `list_nodes()`

```python theme={null}
@abstractmethod
def list_nodes(session_id, *, kind, limit, cursor) -> Page[Node]
```

**session\_id** *required*

**kind** (default: None)

**limit** (default: 1000)

**cursor** (default: None)

**returns** `Page[Node]`

[`Page`](./store#contree_sdk.store.Page)\[[`Node`](./store#contree_sdk.store.Node)]

#### `list_sessions()`

```python theme={null}
@abstractmethod
def list_sessions(*, kind, prefix, limit, cursor) -> Page[SessionInfo]
```

**kind** (default: None)

**prefix** (default: '')

**limit** (default: 1000)

**cursor** (default: None)

**returns** `Page[SessionInfo]`

[`Page`](./store#contree_sdk.store.Page)\[[`SessionInfo`](./store#contree_sdk.store.SessionInfo)]

#### `set_session_ttl()`

```python theme={null}
@abstractmethod
def set_session_ttl(session_id, ttl) -> SessionInfo
```

**session\_id** *required*

**ttl** *required*

**returns** `SessionInfo`

[`SessionInfo`](./store#contree_sdk.store.SessionInfo)
