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
ANode 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
- Async
Contract
create_session()creates an empty tree. Repeating it with the same kind returns the existing tree without renewing its TTL. A different kind raisesValueError.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 raiseNodeConflictErrorwithout 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()returnsNonefor a missing or expired tree. Reading a missing node or appending to a missing tree raisesValueError.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 occupyNoneslots. A missing or expired tree raisesValueError, including for an empty batch. Each returned node contains independent JSON values.list_sessions(),list_nodes(), andlist_children()filter before pagination and order by identity.list_history()follows parents from the specified node. Passnext_cursorwith the same selection;Nonemeans 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=Noneuses the implementation policy; the supplied Memory and SQLite implementations retain the tree until deletion. Zero expires immediately; negative or nonfinite TTL raisesValueError. 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 and Save and resume a session for selection and explicit resume. For a live tree with rootR, 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.
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
Methods
append()
required
required
None
close()
None
create_session()
required
default:"'session'"
default:"None"
SessionInfo
delete_session()
required
bool
get_node()
required
required
get_nodes()
required
required
get_session()
required
SessionInfo | None
SessionInfo | Nonelist_children()
required
required
default:"None"
default:"1000"
default:"None"
list_history()
required
required
default:"1000"
default:"None"
list_nodes()
required
default:"None"
default:"1000"
default:"None"
list_sessions()
default:"None"
default:"''"
default:"1000"
default:"None"
Page[SessionInfo]
set_session_ttl()
required
required
SessionInfo
class AsyncSQLiteStore
Methods
append()
required
required
None
close()
None
create_session()
required
default:"'session'"
default:"None"
SessionInfo
delete_session()
required
bool
get_node()
required
required
get_nodes()
required
required
get_session()
required
SessionInfo | None
SessionInfo | Nonelist_children()
required
required
default:"None"
default:"1000"
default:"None"
list_history()
required
required
default:"1000"
default:"None"
list_nodes()
required
default:"None"
default:"1000"
default:"None"
list_sessions()
default:"None"
default:"''"
default:"1000"
default:"None"
Page[SessionInfo]
set_session_ttl()
required
required
SessionInfo
class AsyncStore
Methods
append()
required
required
None
close()
None
create_session()
required
default:"'session'"
default:"None"
SessionInfo
delete_session()
required
bool
get_node()
required
required
get_nodes()
required
required
get_session()
required
SessionInfo | None
SessionInfo | Nonelist_children()
required
required
default:"None"
default:"1000"
default:"None"
list_history()
required
required
default:"1000"
default:"None"
list_nodes()
required
default:"None"
default:"1000"
default:"None"
list_sessions()
default:"None"
default:"''"
default:"1000"
default:"None"
Page[SessionInfo]
set_session_ttl()
required
required
SessionInfo
class Node
Attributes
str | None
str
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]]]
str
exception NodeConflictError
Attributes
Methods
add_note()
with_traceback()
alias of
strclass Page
class SessionInfo
class SyncMemoryStore
Methods
append()
required
required
None
close()
None
create_session()
required
default:"'session'"
default:"None"
SessionInfo
delete_session()
required
bool
get_node()
required
required
get_nodes()
required
required
get_session()
required
SessionInfo | None
SessionInfo | Nonelist_children()
required
required
default:"None"
default:"1000"
default:"None"
list_history()
required
required
default:"1000"
default:"None"
list_nodes()
required
default:"None"
default:"1000"
default:"None"
list_sessions()
default:"None"
default:"''"
default:"1000"
default:"None"
Page[SessionInfo]
set_session_ttl()
required
required
SessionInfo
class SyncSQLiteStore
Methods
append()
required
required
None
close()
None
create_session()
required
default:"'session'"
default:"None"
SessionInfo
delete_session()
required
bool
get_node()
required
required
get_nodes()
required
required
get_session()
required
SessionInfo | None
SessionInfo | Nonelist_children()
required
required
default:"None"
default:"1000"
default:"None"
list_history()
required
required
default:"1000"
default:"None"
list_nodes()
required
default:"None"
default:"1000"
default:"None"
list_sessions()
default:"None"
default:"''"
default:"1000"
default:"None"
Page[SessionInfo]
set_session_ttl()
required
required
SessionInfo
class SyncStore
Methods
append()
required
required
None
close()
None
create_session()
required
default:"'session'"
default:"None"
SessionInfo
delete_session()
required
bool
get_node()
required
required
get_nodes()
required
required
get_session()
required
SessionInfo | None
SessionInfo | Nonelist_children()
required
required
default:"None"
default:"1000"
default:"None"
list_history()
required
required
default:"1000"
default:"None"
list_nodes()
required
default:"None"
default:"1000"
default:"None"
list_sessions()
default:"None"
default:"''"
default:"1000"
default:"None"
Page[SessionInfo]
set_session_ttl()
required
required
SessionInfo