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

# Advanced: resume a background operation

For ordinary commands, start with [Your first sandbox](https://docs.tokenfactory.nebius.com/sandboxes/sdk/python_sdk/getting-started.md) and its sandbox factory.
This guide exposes lower-level controls. See [Advanced: assemble a sandbox yourself](https://docs.tokenfactory.nebius.com/sandboxes/sdk/python_sdk/manual-sandbox.md) for manual
component assembly and resource ownership.

Use `spawn_detached()` when a command must continue after the current Python
process exits. The session records its UUID, source image, parent entry, branch,
command options, and uploaded attachment metadata. It does not start an event
reader. The server still applies the operation’s configured timeout.

Use a SQLite store for persistence across processes. Memory stores implement the
same API but retain records only while that store instance exists. Keep the same
session ID, database, and endpoint when resuming.

## Submit work

The first block submits work and closes the local store and client. It leaves the
remote operation running. Store the operation UUID in your application or retrieve
it through `session.list_operations()` after restarting Python.

The async variant uses native transport and storage. No event loop or background
reader must remain running between submission and collection. Run async blocks
inside an async function or a compatible notebook.

**Sync**

```python theme={null}
from contree_client import profiles
from contree_client.sync import ContreeClient

from contree_sdk import ContreeSession
from contree_sdk.store import SyncSQLiteStore


with (
    ContreeClient.from_profile(profiles.from_env() or profiles.resolve_profile()) as client,
    SyncSQLiteStore("session.db") as store,
):
    session = ContreeSession(client, image="tag:alpine:latest", session_id="background", store=store)
    record = session.spawn_detached(shell="printf saved > /result.txt; cat /result.txt")
    operation_uuid = record.uuid
```

**Async**

```python theme={null}
from contree_client import profiles
from contree_client.asyncio import ContreeAsyncClient

from contree_sdk import ContreeAsyncSession
from contree_sdk.store import AsyncSQLiteStore


async with (
    ContreeAsyncClient.from_profile(profiles.from_env() or profiles.resolve_profile()) as client,
    AsyncSQLiteStore("async.db") as store,
):
    session = ContreeAsyncSession(client, image="tag:alpine:latest", session_id="background", store=store)
    record = await session.spawn_detached(shell="printf saved > /result.txt; cat /result.txt")
    operation_uuid = record.uuid
```

## Collect the result

Run the next block in the process that collects the result. `wait_operation()`
uses the session’s commit policy and records completion atomically with history.
It returns the process result, including a nonzero exit code. The consumer needs
the same working directory and connection profile. It obtains the UUID from
the store, so no Python variables from the submitting process are needed. This
example has exactly one pending operation; applications with several operations
must select the intended record or accept a UUID as an argument.

**Sync**

```python theme={null}
from contree_client import profiles
from contree_client.sync import ContreeClient

from contree_sdk import ContreeSession
from contree_sdk.store import SyncSQLiteStore


with (
    ContreeClient.from_profile(profiles.from_env() or profiles.resolve_profile()) as client,
    SyncSQLiteStore("session.db") as store,
):
    session = ContreeSession(client, session_id="background", store=store)
    pending = session.list_operations()
    (record,) = pending
    operation_uuid = record.uuid
    result = session.wait_operation(operation_uuid)
    repeated_result = session.wait_operation(operation_uuid)
    assert result == repeated_result
    assert session.list_operations() == ()
    assert len(session.history()[0]) == 2
    assert session.list_operations(pending_only=False)[0].history_id is not None
```

**Async**

```python theme={null}
from contree_client import profiles
from contree_client.asyncio import ContreeAsyncClient

from contree_sdk import ContreeAsyncSession
from contree_sdk.store import AsyncSQLiteStore


async with (
    ContreeAsyncClient.from_profile(profiles.from_env() or profiles.resolve_profile()) as client,
    AsyncSQLiteStore("async.db") as store,
):
    session = ContreeAsyncSession(client, session_id="background", store=store)
    pending = await session.list_operations()
    (record,) = pending
    operation_uuid = record.uuid
    result = await session.wait_operation(operation_uuid)
    repeated_result = await session.wait_operation(operation_uuid)
    assert result == repeated_result
    assert await session.list_operations() == ()
    assert len((await session.history())[0]) == 2
    assert (await session.list_operations(pending_only=False))[0].history_id is not None
```

## Branches and commit policy

Completion keeps the parent captured at spawn time. Switching branches or
continuing other work does not redirect or reject the result. The completed
entry is another child of its original parent. Obtain its ID from
`session.get_operation(uuid).history_id` and use `navigate()` to select it.
Waiting can advance the local position when it still matches the source;
replaying an already completed result does not undo a later rollback.

The default `ApiSuccessCommitPolicy` retains every successful non-disposable
result, including nonzero exit codes. Pass `commit_policy=ZeroExitCommitPolicy()`
when resuming to retain only exit code zero. See [Customize the SDK](https://docs.tokenfactory.nebius.com/sandboxes/sdk/python_sdk/customization.md) for custom
policies. Concurrent completion calls share the first stored decision. A later
policy change or rollback cannot cause a second commit.

Disposable and policy-rejected results remain readable without a history entry.
Final API failures and cancellation remain readable as the same exceptions.
Completed `wait_operation()` calls use the stored response without remote I/O.
Transport errors remain pending. A timeout or cancellation of a live wait follows
`Operation.wait()` semantics and requests remote cancellation; it does not merely
stop observing the operation.

`wait_operation()` closes its internal handle before returning or raising. Async
cleanup completes even after repeated caller cancellation. Synchronous cleanup
uses a bounded reader join; a blocked transport can keep its reader thread alive
until the stream releases it. See [Advanced: control a running sandbox](https://docs.tokenfactory.nebius.com/sandboxes/sdk/python_sdk/operation.md) for lifecycle details.

## Control and inspect registered work

`detach(operation)` registers an existing session operation without closing its
handle. `restore_operation(uuid)` reconstructs a handle through `create_operation()`
with the original context. Override `create_operation_record()` and
`restore_operation_context()` together to persist application-specific request
fields. Both restoration and commit policies use these hooks. Use that handle to send signals, cancel the operation,
or read events. After `wait()`, `commit_result()` on a restored handle uses the
same atomic completion path. Use `wait_operation()` to replay a stored response.

`list_operations()` returns pending records. Set `pending_only=False` to include
completed records. `OperationRecord.history_id` identifies a retained result;
`response` stores the final API response as a JSON object. Records include the effective
command environment and output, but not client credentials. A different client
endpoint is rejected before the UUID is used.

Local file and stdin sources are not serialized or reopened. Attachments retain
their uploaded UUIDs and destination permissions. Restored `OperationContext`
values have `request.files=None` and `request.stdin=None`; inspect `attachments`
and `files` for the submitted inputs. Commit policies must use this persisted
context, rather than source objects. Detached titles and file metadata are fixed
at registration; `commit_result()` cannot override them.

## Failure boundaries and custom stores

Remote submission and local registration are separate operations. If registration
raises normally, `spawn_detached()` requests cancellation and preserves the
registration error. A process crash between remote submission and registration
can leave an unregistered remote operation. Registration does not promise exactly
one remote submission. Retrying submission can start another operation.

A pending node stores the original context and the identity reserved for its
result. Completion appends one immutable node containing the final response and
optional retained image. An identical write can be retried. If concurrent
collectors disagree on commit policy, the first saved completion is retained.
Deleting the tree removes both records; it does not cancel the remote operation.

Custom stores need only the tree contract, including atomic and idempotent
`append()`. They do not implement a second operation registry API. See
[Tree stores](https://docs.tokenfactory.nebius.com/sandboxes/sdk/python_sdk/reference/store.md) for the guarantees required across processes.
