# Sessions (https://tenki.cloud/docs/sandbox/sessions)

> For the complete documentation index, see [llms.txt](https://tenki.cloud/llms.txt)

Create and drive Tenki Sandbox sessions covering lifecycle, command execution, file I/O, port exposure, and SSH access.



A session is a single running sandbox. This page covers the full session surface: lifecycle, command execution, file I/O, ports, and SSH. Pick your tool in any code block, and the selection syncs across the page.

Create a session [#create-a-session]

**Python**
```python
from tenki import Sandbox

sb = Sandbox.create(
    name="demo",
    cpu_cores=4,
    memory_mb=8192,
    allow_inbound=True,
    allow_outbound=True,
    env={"APP_ENV": "dev"},
    metadata={"owner": "alice"},
)
```

`Sandbox.create` waits by default via a single server-held request and returns an exec-ready session with data-plane access primed. Pass `wait=False` to return immediately in `CREATING`. Use `Client().create(...)` instead when you want to manage the client lifecycle yourself.

**TypeScript**
```typescript
const session = await sandbox.create({
  name: "demo",
  cpuCores: 4,
  memoryMb: 8192,
  allowInbound: true,
  allowOutbound: true,
  env: { APP_ENV: "dev" },
  metadata: { owner: "alice" },
});
```

`create()` waits by default via a single server-held request and returns a run-ready session with data-plane access primed. Pass `waitReady: false` to return immediately in `CREATING`.

**Go**
```go
// Create waits by default and returns a RUNNING, exec-ready session.
session, err := client.Create(
  ctx,
  tenkisandbox.WithName("demo"),
  tenkisandbox.WithCPUCores(4),
  tenkisandbox.WithMemoryMB(8192),
  tenkisandbox.WithAllowInbound(true),
  tenkisandbox.WithAllowOutbound(true),
  tenkisandbox.WithEnvs(map[string]string{"APP_ENV": "dev"}),
  tenkisandbox.WithMetadata(map[string]string{"owner": "alice"}),
  tenkisandbox.WithWaitTimeout(3*time.Minute),
)

// Opt out when you want to orchestrate readiness separately.
session, err := client.Create(ctx, tenkisandbox.WithName("demo"), tenkisandbox.WithWaitReady(false))
// Later: session.WaitReady(ctx, 3*time.Minute)
```

**CLI**
```bash
tenki sandbox create \
  --name my-session \
  --cpu 4 \
  --memory-mb 8192 \
  --allow-inbound \
  --allow-outbound \
  --env APP_ENV=dev \
  --metadata owner=alice \
  --metadata purpose=review
```

By default, the CLI waits until the session is `RUNNING` and exec-ready before returning (a single server-held request). Pass `--no-wait` to return immediately.

Every create call takes the same options, named per tool:

| Option           | TypeScript                           | Python                            | Go                                      | CLI                                          |
| ---------------- | ------------------------------------ | --------------------------------- | --------------------------------------- | -------------------------------------------- |
| Name             | `name`                               | `name`                            | `WithName`                              | `--name`                                     |
| CPU / memory     | `cpuCores`, `memoryMb`               | `cpu_cores`, `memory_mb`          | `WithCPUCores`, `WithMemoryMB`          | `--cpu`, `--memory-mb`                       |
| Disk size        | `diskSizeGb`                         | `disk_size_gb`                    | `WithDiskSizeGB`                        | `--disk-size-gb`                             |
| Network          | `allowInbound`, `allowOutbound`      | `allow_inbound`, `allow_outbound` | `WithAllowInbound`, `WithAllowOutbound` | `--allow-inbound`, `--allow-outbound`        |
| Metadata / env   | `metadata`, `env`                    | `metadata`, `env`                 | `WithMetadata`, `WithEnvs`              | `--metadata`, `--env`                        |
| Tags             | `tags`                               | `tags`                            | `WithTags`                              | `--tags`                                     |
| SSH keys         | `sshAuthorizedKeys`                  | `ssh_authorized_keys`             | `WithSSHKeys`                           | `--authorized-key`, `--authorized-keys-file` |
| Volumes          | `volumes`                            | `volumes`                         | `WithVolume`                            | `--volume`                                   |
| Snapshot / image | `snapshotId`, `image`                | `snapshot_id`, `image`            | `WithSnapshot`, `WithImage`             | `--snapshot`, `--image`                      |
| Max duration     | `maxDurationMs`                      | `max_duration`                    | `WithMaxDuration`                       | `--max-duration`                             |
| Idle timeout     | `idleTimeoutMinutes`                 | `idle_timeout_minutes`            | `WithIdleTimeout`                       | `--idle-timeout`                             |
| Pause retention  | `pauseRetentionMs`                   | `pause_retention`                 | `WithPauseRetention`                    | `--pause-retention`                          |
| Sticky           | `sticky`                             | `sticky`                          | `WithSticky`                            | `--sticky`                                   |
| OpenCode         | `enableOpenCode`, `openCodeProvider` | `enable_opencode`                 | `WithOpenCode`, `WithOpenCodeProvider`  | n/a                                          |
| Clone repo       | `cloneRepoUrl`, `githubToken`        | `clone_repo_url`, `github_token`  | `WithCloneRepo`, `WithGitHubToken`      | n/a                                          |
| Wait for ready   | `waitReady`, `waitTimeoutMs`         | `wait`, `timeout`                 | `WithWaitReady`, `WithWaitTimeout`      | `--no-wait`, `--wait-timeout`                |

Unset options fall back to service defaults (2 vCPU, 4096 MB). The CLI enables inbound and outbound networking by default; with the SDKs, set `allowInbound` / `allowOutbound` to enable them.

Manage a session [#manage-a-session]

`WaitReady`/`wait_ready` is only needed for sessions obtained via get/list or created with wait disabled; the create calls above already return ready sessions.

**TypeScript**
```typescript
await session.refresh();
await session.extend(600_000); // +10 minutes
await session.pause();
await session.resume();
await session.close(); // or session.closeIfOpen(), or Symbol.asyncDispose

// List and inspect
const sessions = await sandbox.list();
const s = await sandbox.get(sessionId);
```

**Python**
```python
sb.wait_ready(180)
sb.refresh()
sb.extend(1800)  # +30 minutes (seconds or a timedelta)
sb.pause()
sb.resume()
sb.close()  # or sb.close_if_open(); the context manager closes on exit

# List and inspect
sessions = client.list()
sb = client.get(session_id)
```

**Go**
```go
err = session.WaitReady(ctx, 3*time.Minute)
err = session.Refresh(ctx)
err = session.Extend(ctx, 30*time.Minute)
err = session.Pause(ctx)
err = session.Resume(ctx)
err = session.Close(ctx)
err = session.CloseIfOpen(ctx)

// List and inspect
sessions, err := client.List(ctx)
session, err := client.Get(ctx, sessionID)
```

**CLI**
```bash
# List and inspect
tenki sandbox list
tenki sandbox list --json
tenki sandbox get --session <session-id>

# Pause, resume, terminate
tenki sandbox pause --session <session-id>
tenki sandbox resume --session <session-id>
tenki sandbox terminate --session <session-id>
```

Command execution [#command-execution]

**Python**
```python
# One-shot: collects stdout/stderr and returns a result
result = sb.exec("bash", "-lc", "echo $APP_ENV && make test", env={"APP_ENV": "ci"}, timeout=120)
if not result.ok:
    raise RuntimeError(f"failed: exit={result.exit_code} stderr={result.stderr_text}")

# Or start a live process and stream output
proc = sb.start("npm", "test")
proc.close_stdin()
for chunk in proc.stdout:
    print(chunk.decode(), end="")
proc.wait().check()
```

`exec` accepts `cwd`, `env`, `timeout`, `input`, and `check=True` (raises `CommandFailedError` on a non-zero exit). Use `sb.shell("...")` for shell parsing.

Result helpers: `result.stdout_text`, `result.stderr_text`, `result.ok`, `result.check()`.

**TypeScript**
```typescript
// One-shot
const result = await session.exec("npm", {
  args: ["test"],
  timeoutMs: 60_000,
  onOutput: (chunk) => process.stdout.write(chunk.data),
});

// Or stream explicitly
const stream = await session.stream("npm", { args: ["test"] });
for (;;) {
  const chunk = await stream.next();
  if (!chunk) break;
  process.stdout.write(chunk.data);
}
await stream.wait();
```

**Go**
```go
result, err := session.Exec(
  ctx,
  "bash",
  tenkisandbox.WithArgs("-lc", "echo $APP_ENV && make test"),
  tenkisandbox.WithEnv("APP_ENV", "ci"),
  tenkisandbox.WithTimeout(2*time.Minute),
)
if err != nil {
  log.Fatal(err)
}

if !result.Status.IsSuccess() {
  log.Fatalf("failed: exit=%d stderr=%s", result.ExitCode, result.StderrString())
}
```

Exec options: `WithArgs`, `WithTimeout`, `WithEnv`, `WithEnvs`.

Result helpers: `result.StdoutString()`, `result.StderrString()`, `result.Status.IsSuccess()`, `IsFailed()`, `IsTimedOut()`.

**CLI**
```bash
tenki sandbox exec --session <session-id> -c 'go test ./...'
tenki sandbox exec --session <session-id> --timeout 2m -c 'npm ci && npm test'
```

`-c` (short for `--shell`) runs the line through a shell so `&&`, pipes, redirects and globs work. Without it, `exec` runs the program directly with no shell; to pass flags straight to a program, put them after `--` (for example `tenki sandbox exec -- bash -lc '...'`).

CLI output includes:

* streamed stdout and stderr
* final status, exit code, and duration

File operations [#file-operations]

File operations are rooted in the session's working directory, `/home/tenki`. Relative paths resolve from there; absolute paths outside it (including `/tmp`) are rejected with a permission error.

**Python**
```python
sb.fs.write_text("/home/tenki/config.json", '{"key": "value"}')
data = sb.fs.read_text("/home/tenki/config.json")

# Bytes, directory listing, and local <-> guest transfers
sb.fs.write_bytes("/home/tenki/blob.bin", b"\x00\x01")
entries = sb.fs.list("/home/tenki")
sb.fs.upload("./local.tar", "/home/tenki/local.tar")
sb.fs.download("/home/tenki/build.log", "./build.log")
```

**TypeScript**
```typescript
await session.writeFile("/home/tenki/config.json", '{"key": "value"}');
const data = await session.readFile("/home/tenki/config.json");
```

**Go**
```go
err = session.WriteFile(ctx, "/home/tenki/hello.txt", []byte("hello"))
data, err := session.ReadFile(ctx, "/home/tenki/hello.txt")
```

**CLI**
```bash
# inline
tenki sandbox write --session <session-id> --path /home/tenki/app.env --data 'PORT=3000'

# from a local file
tenki sandbox write --session <session-id> --path /home/tenki/config.json --data-file ./config.json

# from stdin
cat ./local-file.txt | tenki sandbox write --session <session-id> --path /home/tenki/input.txt

# read to stdout
tenki sandbox read --session <session-id> --path /home/tenki/config.json

# read into a local file
tenki sandbox read --session <session-id> --path /home/tenki/build.log --out ./build.log
```

Port exposure and networking [#port-exposure-and-networking]

Each session has independent inbound and outbound settings:

* `allow_outbound=true` lets the guest make outbound network calls
* `allow_inbound=true` enables inbound exposure workflows

**Python**
```python
preview = sb.expose_port(3000, ttl=3600)  # ttl in seconds
print(preview.url)

ports = sb.list_exposed_ports()
sb.unexpose_port(3000)
```

**TypeScript**
```typescript
const port = await session.exposePort(3000, { ttlMs: 3600_000 });
console.log(port.previewUrl);
```

**Go**
```go
port, err := session.ExposePort(ctx, 3000)
ports, err := session.ListExposedPorts(ctx)
err = session.UnexposePort(ctx, 3000)
```

**CLI**
```bash
tenki sandbox expose --session <session-id> --port 3000
tenki sandbox ports --session <session-id>
tenki sandbox unexpose --session <session-id> --port 3000
```

When you expose a long-running server you started with `exec`, background it and detach its streams (`>/tmp/server.log 2>&1 </dev/null &`). `exec` streams the command's output until it closes, so a server that keeps the output stream open leaves `exec` waiting forever.

Use the `previewUrl` returned by each expose call rather than constructing hostnames yourself; the host pattern is not a stable contract.

SSH access [#ssh-access]

Connect to a session over SSH. The CLI gives you an interactive shell, managed local config, and key management; the SDKs expose a raw byte-stream transport for wiring into your own SSH tooling. (Set keys at create time with the `sshAuthorizedKeys` option in the table above.)

**TypeScript**
Raw byte-stream transport for your own SSH tooling, plus replacing the authorized keys on a running session:

```typescript
const conn = await session.ssh();
await conn.write(new TextEncoder().encode("ls -la\n"));
const chunk = await conn.read(); // Uint8Array | null
if (chunk) process.stdout.write(chunk);
conn.close();

// Replace the authorized keys
await session.updateSshAuthorizedKeys(["ssh-ed25519 AAAA..."]);
```

**Python**
Raw byte-stream transport (an `io.RawIOBase`), plus replacing the authorized keys on a running session:

```python
conn = sb.ssh()
conn.write(b"ls -la\n")
print(conn.recv(4096).decode())
conn.close()

# Replace the authorized keys
sb.update_ssh_authorized_keys(["ssh-ed25519 AAAA..."])
```

**Go**
Raw byte-stream transport for your own SSH tooling, plus replacing the authorized keys on a running session:

```go
conn, err := session.SSH(ctx)
defer conn.Close()

// Replace the authorized keys
err = session.UpdateSSHAuthorizedKeys(ctx, []string{"ssh-ed25519 AAAA..."})
```

**CLI**
Open an interactive shell:

```bash
tenki sandbox ssh --session <session-id>
```

Useful flags: `--user` (default `tenki`), `--identity-file`, `--batch-mode`, `--connect-timeout`, `--strict-host-key-checking`. Pass standard SSH arguments after the session ID:

```bash
tenki sandbox ssh <session-id> -L 8080:127.0.0.1:8080
```

Install a managed entry in your local SSH config so you can use friendly aliases:

```bash
tenki sandbox ssh config install
tenki sandbox ssh config status
tenki sandbox ssh config uninstall
ssh sbx-<session-uuid>
```

Replace the authorized keys on a running session:

```bash
tenki sandbox ssh-keys set --session <session-id> --keys-file ~/.ssh/authorized_keys
```

Session metadata [#session-metadata]

Metadata tags a session with arbitrary string key-value pairs (`metadata` in the SDKs, `--metadata key=value` on the CLI, both shown above). Use it to filter sessions in dashboards, attribute billing, or tie a session to an upstream job ID. Metadata is opaque to the service; it is for your bookkeeping.
