# Filesystem Sync
Source: https://docs.terminaluse.com/advanced/filesystem-sync
Python runtime sync for /workspace and task system folders
This page is about runtime sync inside a running agent.
That surface is Python-only today. It is not a CLI command and not a TypeScript SDK feature.
## Filesystem Sync
Use the Python ADK module to sync the attached filesystem into `/workspace`:
```python theme={"dark"}
from terminaluse.lib import adk
await adk.filesystem.sync_down(filesystem_id=ctx.task.filesystem_id)
# read and write files in /workspace
await adk.filesystem.sync_up(filesystem_id=ctx.task.filesystem_id)
```
## Task System Folders
TerminalUse also maintains task-scoped system folders for supported runtimes:
* `dot_claude` mounted at `/root/.claude`
* `dot_codex` mounted at `/root/.codex`
```python theme={"dark"}
await adk.task.sync_down_system_folder(
task_id=ctx.task.id,
folder_type="dot_claude",
)
await adk.task.sync_up_system_folder(
task_id=ctx.task.id,
folder_type="dot_claude",
)
```
If you omit `folder_type`, the task module syncs all supported system folders.
## What Sync Does
* `sync_down` compares remote and local checksums, then fetches and extracts only when needed
* `sync_up` creates a new archive and pushes changes back when needed
* filesystem sync and system-folder sync are both used by the runtime around `on_create` and `on_event`
* `on_cancel` is not wrapped in the automatic sync path, so do not rely on cancel-time workspace writes being persisted
## Important Boundary
Do not use `get_upload_url` or `get_download_url` as if they were per-file APIs inside your agent code. Those endpoints are for whole-filesystem archive transfers.
Use:
* `adk.filesystem.sync_down` / `sync_up` for runtime workspace sync
* `uploadFile` or `downloadFile` for one-file app-side operations
# ADK Reference
Source: https://docs.terminaluse.com/api-reference/adk
Agent Development Kit modules for building agents
The Agent Development Kit (ADK) provides modules for agents running in the agent runtime. These modules handle communication with the platform, state management, filesystem operations, and more.
## Overview
Import ADK modules from `terminaluse.lib`:
```python theme={"dark"}
from terminaluse.lib import adk
```
## Available Modules
| Module | Purpose |
| ------------------------ | ---------------------------------------------------- |
| `adk.messages` | Send and list messages |
| `adk.state` | Create, get, update, delete task state |
| `adk.tasks` | Get and delete tasks |
| `adk.events` | List and get events |
| `adk.agents` | Retrieve agent information |
| `adk.acp` | Agent-to-Client Protocol (create tasks, send events) |
| `adk.filesystem` | Sync files up/down with change detection |
| `adk.agent_task_tracker` | Track agent task progress |
| `adk.task` | Sync task-scoped system folders |
## adk.messages
Send and manage messages within a task.
### send
Send a message to the task.
```python theme={"dark"}
from terminaluse.lib import adk
from terminaluse.lib import TextPart, DataPart
# Simple text message (recommended)
await adk.messages.send(
task_id="task-123",
content="Hello, world!"
)
# Explicit TextPart
await adk.messages.send(
task_id="task-123",
content=TextPart(text="**Bold** and *italic*")
)
# Structured data (for generative UIs)
await adk.messages.send(
task_id="task-123",
content=DataPart(data={"temperature": 72, "unit": "fahrenheit"})
)
```
### list
List messages for a task.
```python theme={"dark"}
messages = await adk.messages.list(task_id="task-123")
for msg in messages:
print(msg.content)
```
## adk.state
Manage persistent state for a task. State is scoped to `(task_id, agent_id)`.
### create
Initialize state when a task starts.
```python theme={"dark"}
await adk.state.create(
task_id="task-123",
agent_id="agent-456",
state={
"user_preferences": {},
"message_count": 0,
"initialized_at": "2024-01-01T00:00:00Z"
}
)
```
### get
Retrieve current state.
```python theme={"dark"}
state = await adk.state.get(
task_id="task-123",
agent_id="agent-456"
)
print(state.get("message_count"))
```
### update
Merge updates into existing state.
```python theme={"dark"}
await adk.state.update(
task_id="task-123",
agent_id="agent-456",
state={
"message_count": 5,
"last_activity": "2024-01-01T12:00:00Z"
}
)
```
### delete
Remove state entirely.
```python theme={"dark"}
await adk.state.delete(
task_id="task-123",
agent_id="agent-456"
)
```
## adk.tasks
Retrieve and manage tasks.
### get
Get a task by ID or name.
```python theme={"dark"}
# By ID
task = await adk.tasks.get(task_id="task-123")
# By name
task = await adk.tasks.get(task_name="my-task")
print(task.id, task.status)
```
### delete
Delete a task.
```python theme={"dark"}
await adk.tasks.delete(task_id="task-123")
```
## adk.events
Access events for a task.
### list
List events for a task.
```python theme={"dark"}
events = await adk.events.list(task_id="task-123")
for event in events:
print(event.content)
```
### get
Get a specific event.
```python theme={"dark"}
event = await adk.events.get(event_id="event-789")
```
## adk.agents
Retrieve agent information.
### get
Get an agent by ID or name.
```python theme={"dark"}
# By ID
agent = await adk.agents.get(agent_id="agent-456")
# By name
agent = await adk.agents.get(agent_name="my-agent")
print(agent.name, agent.status)
```
### list
List agents.
```python theme={"dark"}
agents = await adk.agents.list()
for agent in agents:
print(agent.name)
```
## adk.acp
Agent-to-Client Protocol for creating tasks and sending events programmatically.
### create\_task
Create a new task.
```python theme={"dark"}
task = await adk.acp.create_task(
agent_id="agent-456",
params={"user_id": "user-123", "mode": "interactive"}
)
print(task.id)
```
### send\_event
Send an event to a task.
```python theme={"dark"}
await adk.acp.send_event(
task_id="task-123",
content=TextPart(text="User input")
)
```
### cancel\_task
Cancel a running task.
```python theme={"dark"}
await adk.acp.cancel_task(task_id="task-123")
```
## adk.filesystem
Sync files between the agent runtime and cloud storage.
### sync\_down
Download files from cloud storage to local filesystem.
```python theme={"dark"}
result = await adk.filesystem.sync_down(
filesystem_id="fs-123",
local_path="/workspace/files"
)
print(f"Downloaded {result.files_changed} files")
```
### sync\_up
Upload local files to cloud storage.
```python theme={"dark"}
result = await adk.filesystem.sync_up(
filesystem_id="fs-123",
local_path="/workspace/files"
)
print(f"Uploaded {result.files_changed} files")
```
Features:
* **Manifest-based sync**: Only changed files are transferred
* **Compression**: Files are compressed for efficient transfer
* **Change detection**: Automatically detects modified files
See [Filesystem Sync](/advanced/filesystem-sync) for detailed usage.
## adk.task
Task-scoped system-folder helpers.
### sync\_down\_system\_folder
```python theme={"dark"}
await adk.task.sync_down_system_folder(
task_id="task-123",
folder_type="dot_claude",
)
```
### sync\_up\_system\_folder
```python theme={"dark"}
await adk.task.sync_up_system_folder(
task_id="task-123",
folder_type="dot_claude",
)
```
Use these when you need to manage task-scoped folders like `/root/.claude` or `/root/.codex` separately from `/workspace`.
## adk.agent\_task\_tracker
Track progress of agent tasks.
### get
Get tracker for a task.
```python theme={"dark"}
tracker = await adk.agent_task_tracker.get(task_id="task-123")
print(tracker.status, tracker.progress)
```
### list
List all trackers.
```python theme={"dark"}
trackers = await adk.agent_task_tracker.list()
```
### update
Update tracker progress.
```python theme={"dark"}
await adk.agent_task_tracker.update(
task_id="task-123",
progress=75,
status_message="Processing step 3 of 4"
)
```
## Using with TaskContext
In agent handlers, use `TaskContext` for convenience - it provides pre-bound versions of these modules:
```python theme={"dark"}
from terminaluse.lib import AgentServer, TaskContext, Event
server = AgentServer()
@server.on_event
async def handle_event(ctx: TaskContext, event: Event):
# Using ctx (pre-bound to current task/agent)
await ctx.messages.send("Hello")
await ctx.state.update({"count": 1})
# Equivalent using adk (requires explicit IDs)
from terminaluse.lib import adk
await adk.messages.send(task_id=ctx.task.id, content="Hello")
await adk.state.update(
task_id=ctx.task.id,
agent_id=ctx.agent.id,
state={"count": 1}
)
```
Use `ctx` in handlers for cleaner code. Use `adk` directly in helper functions or when you need to operate on different tasks.
# Delete Agent
Source: https://docs.terminaluse.com/api-reference/agents/delete
api-reference/agents/openapi.json delete /agents/{agent_id}
# Delete Agent by Name
Source: https://docs.terminaluse.com/api-reference/agents/delete-by-name
api-reference/agents/openapi.json delete /agents/name/{namespace_slug}/{agent_name}
# Deploy Agent
Source: https://docs.terminaluse.com/api-reference/agents/deploy
api-reference/agents/openapi.json post /agents/deploy
The deployment is asynchronous. After calling this endpoint, poll `GET /branches/{branch_id}`
for status updates until status is `READY` or `FAILED`.
# List Agents
Source: https://docs.terminaluse.com/api-reference/agents/list
api-reference/agents/openapi.json get /agents
# Get Agent
Source: https://docs.terminaluse.com/api-reference/agents/retrieve
api-reference/agents/openapi.json get /agents/{agent_id}
# Get Agent by Name
Source: https://docs.terminaluse.com/api-reference/agents/retrieve-by-name
api-reference/agents/openapi.json get /agents/name/{namespace_slug}/{agent_name}
# Create API Key
Source: https://docs.terminaluse.com/api-reference/api-keys/create
api-reference/api-keys/openapi.json post /api_keys
Store the returned `api_key` securely - it will not be shown again.
# Get API Key
Source: https://docs.terminaluse.com/api-reference/api-keys/get
api-reference/api-keys/openapi.json get /api_keys/{api_key_id}
# List API Keys
Source: https://docs.terminaluse.com/api-reference/api-keys/list
api-reference/api-keys/openapi.json get /api_keys
# Revoke API Key
Source: https://docs.terminaluse.com/api-reference/api-keys/revoke
api-reference/api-keys/openapi.json delete /api_keys/{api_key_id}
Revoking a key immediately disables its access:
1. Key is marked as revoked (cannot authenticate)
2. Auth cache is evicted (immediate effect)
3. Scope tuples are cleaned up (best-effort)
4. Key is removed from all sharing groups
# Update API Key Scopes
Source: https://docs.terminaluse.com/api-reference/api-keys/update-scopes
api-reference/api-keys/openapi.json patch /api_keys/{api_key_id}
Each scope grants a role on a specific resource:
```json theme={"dark"}
{
"resource_type": "agent",
"resource_id": "agt_xxx",
"role": "editor"
}
```
### Valid Roles by Resource Type
| Resource Type | Valid Roles |
| ------------- | ----------------------------------------------------- |
| `org` | `admin`, `member` |
| `namespace` | `admin` |
| `project` | `discoverer`, `viewer`, `editor`, `owner` |
| `agent` | `discoverer`, `viewer`, `editor`, `owner`, `executor` |
The `executor` role is legacy and is normalized to `viewer` internally, but still grants `run` permission.
# Update API Key Sharing Groups
Source: https://docs.terminaluse.com/api-reference/api-keys/update-sharing-groups
api-reference/api-keys/openapi.json patch /api_keys/{api_key_id}/sharing-groups
When an API key creates a resource (agent or project), sharing groups automatically receive `owner` access to that resource.
**How it works:**
1. **Key creation** - The key's service user is added as a member to each sharing group
2. **Resource creation** - When the key creates an agent/project, each sharing group gets `owner` role
3. **Effect** - All members of the sharing groups automatically get owner access
A key with no sharing groups can create resources that only it can access. Always assign at least one sharing group for team visibility.
# Authentication
Source: https://docs.terminaluse.com/api-reference/authentication
API authentication
Authenticate using a Bearer Token:
```python Python theme={"dark"}
from terminaluse import TerminalUse
# Explicit token
client = TerminalUse(token="tu_your_token_here")
# Or via environment variable TERMINALUSE_API_KEY
client = TerminalUse()
```
```typescript TypeScript theme={"dark"}
import { TerminalUseClient } from '@terminaluse/sdk';
// Explicit token
const client = new TerminalUseClient({ bearerAuth: { token: 'tu_your_token_here' } });
// Or via environment variable TERMINALUSE_API_KEY
const client = new TerminalUseClient();
```
```bash CLI theme={"dark"}
# Interactive login (recommended)
tu login
# Or set environment variable
export TERMINALUSE_API_KEY=tu_your_token_here
```
## Getting Tokens
1. Run `tu login` to authenticate via browser
2. Or get a token from the [dashboard](https://app.terminaluse.com)
# Get Branch
Source: https://docs.terminaluse.com/api-reference/branches-get
api-reference/openapi.json get /branches/{branch_id}
# List Branches
Source: https://docs.terminaluse.com/api-reference/branches-list
api-reference/openapi.json get /agents/{namespace_slug}/{agent_name}/branches
# List Branch Events
Source: https://docs.terminaluse.com/api-reference/branches-list-events
api-reference/openapi.json get /branches/{branch_id}/events
# List Environment Branches
Source: https://docs.terminaluse.com/api-reference/branches-list-for-environment
api-reference/openapi.json get /agents/{namespace_slug}/{agent_name}/environments/{env_name}/branches
# List Branch Versions
Source: https://docs.terminaluse.com/api-reference/branches-list-versions
api-reference/openapi.json get /branches/{branch_id}/versions
# Redeploy Branch
Source: https://docs.terminaluse.com/api-reference/branches-redeploy
api-reference/openapi.json post /branches/{branch_id}/redeploy
# Rollback Branch
Source: https://docs.terminaluse.com/api-reference/branches-rollback
api-reference/openapi.json post /branches/{branch_id}/rollback
# Rollback Named Branch
Source: https://docs.terminaluse.com/api-reference/branches-rollback-by-name
api-reference/openapi.json post /agents/{namespace_slug}/{agent_name}/branches/{branch}/rollback
# CLI Reference
Source: https://docs.terminaluse.com/api-reference/cli
Verified commands and flags for the tu CLI
The `tu` CLI is distributed with the `terminaluse` Python package. Recommended installation:
```bash theme={"dark"}
uv tool install terminaluse
```
If `tu` is not found, run `uv tool update-shell` and restart your shell once.
## Global Flags
| Flag | Short | Meaning |
| ----------- | ----- | ------------------------- |
| `--version` | `-v` | Show CLI version |
| `--debug` | `-d` | Enable debug output |
| `--json` | `-j` | Output CLI errors as JSON |
| `--help` | `-h` | Show help |
## Top-Level Commands
| Command | Purpose |
| --------------- | ---------------------------------------- |
| `tu login` | Authenticate with TerminalUse |
| `tu logout` | Clear stored credentials |
| `tu whoami` | Show current auth state |
| `tu init` | Scaffold a new agent project |
| `tu deploy` | Build and deploy an agent |
| `tu ls` | List recent versions or branch events |
| `tu rollback` | Roll a branch back to an earlier version |
| `tu logs` | View agent logs |
| `tu agents` | Manage agents |
| `tu tasks` | Manage tasks |
| `tu projects` | Manage projects |
| `tu namespaces` | List namespaces |
| `tu env` | Manage environment variables |
| `tu fs` | Manage filesystems |
| `tu keys` | Manage webhook keys |
## Authentication
```bash theme={"dark"}
tu login
tu logout
tu whoami
```
## Workflow Commands
### tu init
```bash theme={"dark"}
tu init [--namespace ] [--name ] [--description ] [--template ] [--no-uv] [--sdk-type ]
```
### tu deploy
```bash theme={"dark"}
tu deploy [--config config.yaml] [--tag ] [--branch ] [--skip-build] [--no-cache] [--yes] [--verbose] [--detach]
```
### tu ls
```bash theme={"dark"}
tu ls
tu ls
```
### tu rollback
```bash theme={"dark"}
tu rollback [--branch ] [--version ] [--agent ] [--config ] [--yes] [--json]
```
### tu logs
```bash theme={"dark"}
tu logs
tu logs
```
Useful flags:
* `--task`
* `--level`
* `--source`
* `--since`
* `--until`
* `--version`
* `--limit`
* `--follow`
If you omit the agent argument, `tu logs` tries to resolve it from `config.yaml` in the current directory.
## tu tasks
Verified task subcommands:
* `tu tasks create`
* `tu tasks send`
* `tu tasks ls`
* `tu tasks get`
* `tu tasks pull`
* `tu tasks delete`
* `tu tasks cleanup`
* `tu tasks migrate`
Examples:
```bash theme={"dark"}
tu tasks create -a -p -m "hello"
tu tasks send -m "follow up"
tu tasks get
tu tasks pull --out ./output
```
`tu tasks pull` downloads:
* `/workspace`
* task system folders such as `/.claude` when present
## tu fs
Verified filesystem subcommands:
* `tu fs create`
* `tu fs ls`
* `tu fs get`
* `tu fs push`
* `tu fs pull`
Examples:
```bash theme={"dark"}
tu fs create -p -n my-files
tu fs push ./local-dir
tu fs pull ./local-dir
```
There is no `tu fs sync-down`, `tu fs sync-up`, `tu fs upload-url`, or `tu fs download-url` command.
## tu env
Verified environment subcommands:
* `tu env add`
* `tu env ls`
* `tu env get`
* `tu env rm`
* `tu env pull`
* `tu env import`
Example:
```bash theme={"dark"}
tu env add API_KEY -v "secret" -e prod --secret
```
## Other Resource Commands
### tu agents
Verified agent subcommands:
* `tu agents ls`
* `tu agents get`
* `tu agents delete`
* `tu agents cleanup_workflows`
### tu projects
Verified project subcommands:
* `tu projects ls`
* `tu projects get`
* `tu projects create`
* `tu projects update`
* `tu projects delete`
### tu namespaces
Verified namespace subcommands:
* `tu namespaces ls`
### tu keys
Verified webhook-key subcommands:
* `tu keys instructions`
* `tu keys add`
* `tu keys ls`
* `tu keys rm`
`tu keys` manages webhook keys, not API auth keys.
# Errors
Source: https://docs.terminaluse.com/api-reference/errors
API error handling
## Error Handling
```python Python theme={"dark"}
from terminaluse import TerminalUse, UnprocessableEntityError
client = TerminalUse()
try:
agent = client.agents.retrieve("id")
except UnprocessableEntityError as e:
print(f"Validation error: {e}")
except Exception as e:
print(f"Error: {e}")
```
```typescript TypeScript theme={"dark"}
import { TerminalUseClient, TerminalUseError } from '@terminaluse/sdk';
const client = new TerminalUseClient({
environment: process.env.TERMINALUSE_BASE_URL ?? 'https://api.terminaluse.com',
bearerAuth: { token: process.env.TERMINALUSE_API_KEY! },
});
try {
const agent = await client.agents.retrieve({ agent_id: 'id' });
} catch (e) {
if (e instanceof TerminalUseError) {
console.log(`Error ${e.statusCode}: ${e.message}`);
}
}
```
## Retries
The SDK automatically retries on 408, 429, and 5xx errors (default: 2 retries).
```python theme={"dark"}
client = TerminalUse(max_retries=5) # or 0 to disable
```
```typescript theme={"dark"}
const client = new TerminalUseClient({
environment: process.env.TERMINALUSE_BASE_URL ?? 'https://api.terminaluse.com',
bearerAuth: { token: process.env.TERMINALUSE_API_KEY! },
maxRetries: 5,
});
```
# Get Event
Source: https://docs.terminaluse.com/api-reference/events-get
api-reference/openapi.json get /events/{event_id}
# List Events
Source: https://docs.terminaluse.com/api-reference/events-list
api-reference/openapi.json get /events
# Create Filesystem
Source: https://docs.terminaluse.com/api-reference/filesystems/create
api-reference/filesystems/openapi.json post /filesystems
Filesystems are linked to a project and inherit permissions from that project.
## CLI Usage
```bash CLI theme={"dark"}
tu fs create --project-id proj-123
# With optional name and directory to push
tu fs create -p proj-123 --name my-workspace --dir ./local-files
# JSON output
tu fs create -p proj-123 -j
```
# Single-File Download
Source: https://docs.terminaluse.com/api-reference/filesystems/file-download
api-reference/filesystems/openapi.json get /filesystems/{filesystem_id}/file-download
This endpoint downloads one file from a filesystem by relative path.
Use it when you want file bytes for a single path such as `output/report.md`.
## SDK Surfaces
### TypeScript
```typescript theme={"dark"}
const response = await client.filesystems.downloadFile({
filesystem_id: 'fs_123',
path: 'output/report.md',
});
```
### Python
```python theme={"dark"}
chunks = client.filesystems.download_file(
filesystem_id,
path="output/report.md",
)
```
If you need the entire filesystem instead, use [Get Download URL](/api-reference/filesystems/get-download-url) or `tu fs pull`.
# Get Download URL
Source: https://docs.terminaluse.com/api-reference/filesystems/get-download-url
api-reference/filesystems/openapi.json post /filesystems/{filesystem_id}/download-url
This endpoint returns a presigned URL for downloading the entire filesystem archive directly from storage.
Use it when you want a storage-backed archive download, not when you want one file by relative path.
## What This Endpoint Is For
* whole-filesystem download
* direct-from-storage archive transfer
* flows implemented by `tu fs pull` and the Python ADK sync internals
## What This Endpoint Is Not For
* downloading one file by relative path
* replacing `downloadFile` or `download_file`
* a dedicated CLI command
For one-file downloads, use [Single-File Download](/api-reference/filesystems/file-download).
For a higher-level CLI experience, use `tu fs pull`.
# Get File
Source: https://docs.terminaluse.com/api-reference/filesystems/get-file
api-reference/filesystems/openapi.json get /filesystems/{filesystem_id}/files/{file_path}
Retrieve a specific file (or directory) from the filesystem manifest.
# Get Upload URL
Source: https://docs.terminaluse.com/api-reference/filesystems/get-upload-url
api-reference/filesystems/openapi.json post /filesystems/{filesystem_id}/upload-url
This endpoint returns a presigned URL for uploading the entire filesystem archive directly to storage.
Use it when you already have a prepared compressed archive, typically `tar.zst`, and want to upload that archive without proxying the bytes through the TerminalUse API.
## What This Endpoint Is For
* whole-filesystem upload
* direct-to-storage archive transfer
* flows implemented by `tu fs push` and the Python ADK sync internals
## What This Endpoint Is Not For
* uploading one file by relative path
* replacing `uploadFile`
* a dedicated CLI command
For one-file writes, use [Upload File](/api-reference/filesystems/upload-file).
For a higher-level CLI experience, use `tu fs push`.
# List Filesystems
Source: https://docs.terminaluse.com/api-reference/filesystems/list
api-reference/filesystems/openapi.json get /filesystems
## CLI Usage
```bash CLI theme={"dark"}
# List all filesystems (across all projects)
tu fs ls
# List filesystems for a specific project
tu fs ls --project-id proj-123
# JSON output
tu fs ls -j
```
# List Files
Source: https://docs.terminaluse.com/api-reference/filesystems/list-files
api-reference/filesystems/openapi.json get /filesystems/{filesystem_id}/files
List files in the filesystem manifest with optional filtering by directory, MIME type, and pagination.
# Get Filesystem
Source: https://docs.terminaluse.com/api-reference/filesystems/retrieve
api-reference/filesystems/openapi.json get /filesystems/{filesystem_id}
## CLI Usage
```bash CLI theme={"dark"}
tu fs get fs-uuid
# JSON output
tu fs get fs-uuid -j
```
# Complete Sync Operation
Source: https://docs.terminaluse.com/api-reference/filesystems/sync-complete
api-reference/filesystems/openapi.json post /filesystems/{filesystem_id}/sync-complete
Notify that a sync operation completed and provide the file manifest. This is typically called after uploading or downloading files to finalize the sync state.
# Upload File
Source: https://docs.terminaluse.com/api-reference/filesystems/upload-file
api-reference/filesystems/openapi.json put /filesystems/{filesystem_id}/file-upload
Upload a single file into a filesystem by relative path.
For most calls, provide only `path` plus the raw file bytes.
`writeMode` defaults to `overwrite`.
This is the single-file write surface. Use [Get Upload URL](/api-reference/filesystems/get-upload-url) only for whole-filesystem archive uploads.
There is no dedicated `tu fs` CLI subcommand for single-file upload today.
## Required Inputs
* `path`: Relative file path (for example `src/app.py`).
* Request body: Raw bytes (`Content-Type: application/octet-stream`).
## Version Fields
| Field | Meaning | Required when |
| ----------------- | ----------------------------------------------- | ------------------------------------ |
| `writeMode` | Write strategy: `overwrite` or `conditional` | Optional (`overwrite` is default) |
| `baseVersion` | Expected current archive version | `writeMode=conditional` |
| `expectedVersion` | Expected current file version at `path` | `writeMode=conditional` replace flow |
| `createOnly` | Create only if file does not exist (`"*"` only) | `writeMode=conditional` create flow |
`baseVersion` and `expectedVersion` come from previous reads/writes:
* `baseVersion`: previous `archive_version` response field (or current filesystem archive checksum).
* `expectedVersion`: previous `file_version` response field (or current file checksum).
## Status Codes and Retry Guidance
| Status | Meaning | What to do |
| ------ | ------------------------------------------------------------------------------- | ---------------------------------------- |
| `200` | Existing file replaced | Success |
| `201` | New file created | Success |
| `400` | Invalid path or digest (or malformed request) | Fix request; do not retry unchanged |
| `404` | Filesystem not found or not authorized | Verify ID and access |
| `409` | Conflict (stale `baseVersion` or idempotency key reused with different request) | Refresh state or use new idempotency key |
| `412` | `expectedVersion` precondition failed | Refresh file version and retry |
| `428` | Missing/invalid conditional preconditions | Add required conditional fields |
Use `writeMode=conditional` when you need optimistic concurrency:
* Replace flow: send `baseVersion` + `expectedVersion`.
* Create flow: send `baseVersion` + `createOnly="*"`.
Use `Idempotency-Key` for safe retries:
* Same key + same request -> original response is replayed.
* Same key + different request -> `409` conflict.
# Add Group Manager
Source: https://docs.terminaluse.com/api-reference/groups/add-manager
api-reference/groups/openapi.json put /groups/{group_id}/managers/{member_id}
Managers can add/remove group members and edit group settings. The group creator is automatically assigned as the first manager.
# Add Group Member
Source: https://docs.terminaluse.com/api-reference/groups/add-member
api-reference/groups/openapi.json put /groups/{group_id}/members/{member_id}
Add users or service accounts as group members. When creating an API key with `sharing_group_ids`, the key's service user is automatically added as a member to each specified group.
# Add Group Visible Org
Source: https://docs.terminaluse.com/api-reference/groups/add-visible-org
api-reference/groups/openapi.json put /groups/{group_id}/visible-orgs/{org_id}
Adding a visible org enables cross-organization sharing. Members from visible orgs can see the group but cannot modify it without the manager role.
# Create Group
Source: https://docs.terminaluse.com/api-reference/groups/create
api-reference/groups/openapi.json post /groups
# Delete Group
Source: https://docs.terminaluse.com/api-reference/groups/delete
api-reference/groups/openapi.json delete /groups/{group_id}
Deleting a group removes all transitive permissions granted through group membership. Ensure no critical access depends on the group before deleting.
# Get Group
Source: https://docs.terminaluse.com/api-reference/groups/get
api-reference/groups/openapi.json get /groups/{group_id}
# List Groups
Source: https://docs.terminaluse.com/api-reference/groups/list
api-reference/groups/openapi.json get /groups
# List Group Managers
Source: https://docs.terminaluse.com/api-reference/groups/list-managers
api-reference/groups/openapi.json get /groups/{group_id}/managers
# List Group Members
Source: https://docs.terminaluse.com/api-reference/groups/list-members
api-reference/groups/openapi.json get /groups/{group_id}/members
# List Group Visible Orgs
Source: https://docs.terminaluse.com/api-reference/groups/list-visible-orgs
api-reference/groups/openapi.json get /groups/{group_id}/visible-orgs
# Remove Group Manager
Source: https://docs.terminaluse.com/api-reference/groups/remove-manager
api-reference/groups/openapi.json delete /groups/{group_id}/managers/{member_id}
# Remove Group Member
Source: https://docs.terminaluse.com/api-reference/groups/remove-member
api-reference/groups/openapi.json delete /groups/{group_id}/members/{member_id}
# Remove Group Visible Org
Source: https://docs.terminaluse.com/api-reference/groups/remove-visible-org
api-reference/groups/openapi.json delete /groups/{group_id}/visible-orgs/{org_id}
The owner org (where the group was created) cannot be removed from visible orgs.
# Update Group
Source: https://docs.terminaluse.com/api-reference/groups/update
api-reference/groups/openapi.json patch /groups/{group_id}
# Get Message
Source: https://docs.terminaluse.com/api-reference/messages-get
api-reference/openapi.json get /v2/messages/{message_id}
# List Messages
Source: https://docs.terminaluse.com/api-reference/messages-list
api-reference/openapi.json get /v2/messages
# Check Slug Availability
Source: https://docs.terminaluse.com/api-reference/namespaces/check-slug-availability
api-reference/namespaces/openapi.json get /namespaces/slug-availability/{slug}
Check if a namespace slug is available before creating a namespace. Slugs must be unique across all namespaces and follow URL-friendly formatting rules (lowercase alphanumeric characters, hyphens, and underscores).
# Create Namespace
Source: https://docs.terminaluse.com/api-reference/namespaces/create
api-reference/namespaces/openapi.json post /namespaces
Namespaces provide multi-tenant isolation for your agents and resources. Each namespace gets its own:
* Kubernetes namespace for agent workloads
* GCS bucket for file storage
* Service account for GCP access
# Delete Namespace
Source: https://docs.terminaluse.com/api-reference/namespaces/delete
api-reference/namespaces/openapi.json delete /namespaces/{namespace_id}
The namespace must be empty (no agents, projects, or resources) before it can be deleted.
# Get Namespace
Source: https://docs.terminaluse.com/api-reference/namespaces/get
api-reference/namespaces/openapi.json get /namespaces/{namespace_id}
# Get Namespace by Slug
Source: https://docs.terminaluse.com/api-reference/namespaces/get-by-slug
api-reference/namespaces/openapi.json get /namespaces/slug/{slug}
# List Namespaces
Source: https://docs.terminaluse.com/api-reference/namespaces/list
api-reference/namespaces/openapi.json get /namespaces
# Retry Namespace Provisioning
Source: https://docs.terminaluse.com/api-reference/namespaces/retry-provisioning
api-reference/namespaces/openapi.json post /namespaces/{namespace_id}/retry-provisioning
Use this endpoint to retry provisioning for a namespace that failed or is stuck in a pending state. The `force` parameter allows retrying even if the namespace status is `PENDING`, which is useful for stuck provisioning jobs.
# Update Namespace
Source: https://docs.terminaluse.com/api-reference/namespaces/update
api-reference/namespaces/openapi.json patch /namespaces/{namespace_id}
# Create Project
Source: https://docs.terminaluse.com/api-reference/projects/create
api-reference/projects/openapi.json post /projects
Projects organize agents and resources within a namespace. Each project can contain multiple agents and filesystems.
# Delete Project
Source: https://docs.terminaluse.com/api-reference/projects/delete
api-reference/projects/openapi.json delete /projects/{project_id}
The project must be empty (no agents or filesystems) before it can be deleted.
# Get Project
Source: https://docs.terminaluse.com/api-reference/projects/get
api-reference/projects/openapi.json get /projects/{project_id}
# List Projects
Source: https://docs.terminaluse.com/api-reference/projects/list
api-reference/projects/openapi.json get /projects
# List Project Collaborators
Source: https://docs.terminaluse.com/api-reference/projects/list-collaborators
api-reference/projects/openapi.json get /projects/{project_id}/collaborators
List all explicit collaborator roles assigned to a project. This returns users who have been directly granted access to the project, separate from access inherited through namespace or organization membership.
# Remove Project Collaborator
Source: https://docs.terminaluse.com/api-reference/projects/remove-collaborator
api-reference/projects/openapi.json delete /projects/{project_id}/collaborators/{member_id}
Remove explicit collaborator role from a project. This does not affect access the user may have through namespace or organization membership.
# Set Project Collaborator Role
Source: https://docs.terminaluse.com/api-reference/projects/set-collaborator
api-reference/projects/openapi.json put /projects/{project_id}/collaborators/{member_id}
Create or update an explicit collaborator role on a project. This allows granting project-specific access to users without changing their namespace or organization roles.
# Update Project
Source: https://docs.terminaluse.com/api-reference/projects/update
api-reference/projects/openapi.json patch /projects/{project_id}
# Create State
Source: https://docs.terminaluse.com/api-reference/states-create
api-reference/openapi.json post /states
# Delete State
Source: https://docs.terminaluse.com/api-reference/states-delete
api-reference/openapi.json delete /states/{state_id}
# Get State
Source: https://docs.terminaluse.com/api-reference/states-get
api-reference/openapi.json get /states/{state_id}
# List States
Source: https://docs.terminaluse.com/api-reference/states-list
api-reference/openapi.json get /states
# Update State
Source: https://docs.terminaluse.com/api-reference/states-update
api-reference/openapi.json put /states/{state_id}
# Cancel Task
Source: https://docs.terminaluse.com/api-reference/tasks/cancel
api-reference/tasks/openapi.json post /tasks/{task_id}/cancel
**Behavior:**
* Sets task status to `CANCELLED`
* Stops the running agent execution
* Any pending events are discarded
# Create Task
Source: https://docs.terminaluse.com/api-reference/tasks/create
api-reference/tasks/openapi.json post /tasks
To send messages to a running task, use `sendEvent()` after creation. The `create()` method initializes the task but does not accept message content directly.
# Delete Task
Source: https://docs.terminaluse.com/api-reference/tasks/delete
api-reference/tasks/openapi.json delete /tasks/{task_id}
# List Tasks
Source: https://docs.terminaluse.com/api-reference/tasks/list
api-reference/tasks/openapi.json get /tasks
# Migrate Tasks
Source: https://docs.terminaluse.com/api-reference/tasks/migrate
api-reference/tasks/openapi.json post /tasks/migrate
Migrate tasks between versions within the same branch. Supports two modes:
* **Bulk migration**: Migrate all tasks from a source version (`from_version_id`)
* **Specific tasks**: Migrate specific tasks (`task_ids`)
Target can be explicit (`to_version_id`) or the latest active version (`to_latest`).
# Retrieve Task
Source: https://docs.terminaluse.com/api-reference/tasks/retrieve
api-reference/tasks/openapi.json get /tasks/{task_id}
# Send Event
Source: https://docs.terminaluse.com/api-reference/tasks/send-event
api-reference/tasks/openapi.json post /tasks/{task_id}/events
## Content Types
### TextPart
```json theme={"dark"}
{
"type": "text",
"text": "Your message content"
}
```
### DataPart
```json theme={"dark"}
{
"type": "data",
"data": { "key": "value" }
}
```
# Stream Events
Source: https://docs.terminaluse.com/api-reference/tasks/stream
api-reference/tasks/openapi.json get /tasks/{task_id}/stream
**SSE Behavior:**
* Supports automatic reconnection with `Last-Event-ID` header
* When the client reconnects, the server resumes from the last received event
* Stream terminates with `[DONE]` message
## Stream Event Types
The stream returns `TextStreamPartWrapper` events with a discriminated `type` field. These events are compatible with the Vercel AI SDK format.
| Event Type | Description |
| ------------------ | ------------------------------------------------- |
| `start` | Marks the beginning of the streaming session |
| `start-step` | Marks the beginning of an agentic step |
| `text-start` | Marks the beginning of a text block |
| `text-delta` | Incremental text content |
| `text-end` | Marks the end of a text block |
| `reasoning-start` | Marks the beginning of reasoning/thinking content |
| `reasoning-delta` | Incremental reasoning content |
| `reasoning-end` | Marks the end of reasoning content |
| `tool-input-start` | Marks the beginning of tool input streaming |
| `tool-input-delta` | Incremental tool input (JSON streaming) |
| `tool-input-end` | Marks the end of tool input |
| `tool-call` | Complete tool call with parsed input |
| `tool-result` | Tool execution result |
| `finish-step` | Marks the end of an agentic step |
| `finish` | Marks the end of the streaming session |
| `error` | Error during streaming |
### text-delta
Incremental text content from the agent.
```typescript theme={"dark"}
interface TextDeltaPart {
type: 'text-delta';
id: string; // Text block ID
text: string; // Text delta content
metadata?: {
parentToolUseId?: string; // For subagent context
};
}
```
### tool-call
Complete tool call with parsed input arguments.
```typescript theme={"dark"}
interface ToolCallPart {
type: 'tool-call';
toolCallId: string; // Unique tool call ID
toolName: string; // Name of the tool
input: Record; // Parsed input arguments
metadata?: {
parentToolUseId?: string;
};
}
```
### tool-result
Tool execution result.
```typescript theme={"dark"}
interface ToolResultPart {
type: 'tool-result';
toolCallId: string; // Tool call ID
toolName: string; // Name of the tool
output?: unknown; // Tool output
metadata?: {
parentToolUseId?: string;
};
}
```
### finish
Marks the end of the streaming session.
```typescript theme={"dark"}
interface FinishPart {
type: 'finish';
finishReason: 'stop' | 'tool-calls' | 'length' | 'content-filter' | 'error' | 'other';
totalUsage: {
inputTokens: number;
outputTokens: number;
totalTokens?: number;
};
metadata?: {
cost?: number;
durationMs?: number;
};
}
```
### error
Error during streaming.
```typescript theme={"dark"}
interface StreamErrorPart {
type: 'error';
error: string; // Error message
rawContent?: unknown; // Original content that caused the error
}
```
These event types are designed to be compatible with the [Vercel AI SDK](https://sdk.vercel.ai/docs) streaming format, making it easy to integrate with AI SDK-based applications.
# Update Task
Source: https://docs.terminaluse.com/api-reference/tasks/update
api-reference/tasks/openapi.json put /tasks/{task_id}
# Get Version
Source: https://docs.terminaluse.com/api-reference/versions-get
api-reference/openapi.json get /versions/{version_id}
# List Versions
Source: https://docs.terminaluse.com/api-reference/versions-list
api-reference/openapi.json get /agents/{namespace_slug}/{agent_name}/versions
# Create Webhook Key
Source: https://docs.terminaluse.com/api-reference/webhook-keys-create
api-reference/openapi.json post /agent_api_keys
# Delete Webhook Key
Source: https://docs.terminaluse.com/api-reference/webhook-keys-delete
api-reference/openapi.json delete /agent_api_keys/{id}
# Delete Webhook Key By Name
Source: https://docs.terminaluse.com/api-reference/webhook-keys-delete-by-name
api-reference/openapi.json delete /agent_api_keys/name/{api_key_name}
# Get Webhook Key
Source: https://docs.terminaluse.com/api-reference/webhook-keys-get
api-reference/openapi.json get /agent_api_keys/{id}
# Get Webhook Key By Name
Source: https://docs.terminaluse.com/api-reference/webhook-keys-get-by-name
api-reference/openapi.json get /agent_api_keys/name/{name}
# List Webhook Keys
Source: https://docs.terminaluse.com/api-reference/webhook-keys-list
api-reference/openapi.json get /agent_api_keys
# Access Control
Source: https://docs.terminaluse.com/concepts/access-control
How agent access, filesystem access, and task access interact
TerminalUse access control matters most when you understand tasks and filesystems together.
## The Important Rule
Tasks have dual-parent authorization.
To access a task, TerminalUse evaluates both:
* the parent agent
* the parent filesystem path
In practice, task visibility depends on agent access and filesystem/project access together.
## Creating A Task
When you create a task:
* agent access is checked with `run`
* filesystem access is checked separately
For an existing filesystem:
* `read` permission is enough to attach it to a task
* `update` permission determines whether `/workspace` is writable
For `project_id`-based auto-created filesystems:
* `update` permission on the project is required
* the resulting `/workspace` mount is writable
## Why /workspace Can Be Read-Only
If the caller can run the agent and read the filesystem but cannot update the filesystem, TerminalUse still allows the task to run, but mounts `/workspace` read-only.
That behavior is intentional. It lets users inspect or analyze files through an agent without granting write access to the underlying filesystem.
## Resource Hierarchy
The relevant hierarchy is:
```text theme={"dark"}
Organization
└── Namespace
├── Project
│ └── Filesystem
│ └── Task
└── Agent
└── Task
```
Projects are the main permission boundary for filesystems. Filesystems inherit from their parent project.
## API Keys, Users, And Groups
Users and API keys both operate inside this authorization model.
Use groups when you want to grant the same project or agent access to multiple humans or service principals.
## Practical Consequences
* Agent access alone does not imply filesystem write access.
* Filesystem read access can still allow task execution with a read-only workspace.
* Sharing a project effectively shares its filesystems.
See [Keys and Secrets](/concepts/keys-and-secrets) for the difference between API keys, webhook keys, and runtime secrets.
# Core Model
Source: https://docs.terminaluse.com/concepts/core-model
The primitives you work with in TerminalUse
TerminalUse is easiest to understand when you start from the resource model instead of the API surface.
## The Primitives
| Primitive | What it is | Why it matters |
| ------------- | ------------------------------------------------------------ | --------------------------------------------------------------- |
| `Namespace` | Your isolation boundary for compute and storage | Most teams start with one namespace |
| `Project` | A collaboration and permission boundary for filesystems | Useful for customer- or workflow-level access control |
| `Filesystem` | Persistent files mounted into tasks at `/workspace` | Shared storage across tasks |
| `Agent` | Your deployed Python runtime | Each deploy creates a new version |
| `Environment` | A named deployment policy, such as `production` or `preview` | Branch rules resolve branches into environments |
| `Branch` | A deployment slot for a git branch | A branch points at the current active version |
| `Version` | One deployed build of an agent | Tasks are created against a specific version |
| `Task` | One running conversation or unit of work | Holds state, messages, events, and an optional filesystem mount |
| `Event` | Input sent to a task | Usually user text or structured data |
| `Message` | Output emitted by the agent | Usually assistant text, UI parts, or streamed tool output |
| `State` | Per-task persisted JSON | Used for continuity across turns |
## The Two Loops
### Deploy Loop
1. Write or change agent code.
2. Run `tu deploy`.
3. TerminalUse creates a new version.
4. That version becomes active on the branch's environment.
See [Deployments](/concepts/deployments).
### Run Loop
1. Create or choose a filesystem.
2. Create a task against an agent.
3. Send events to the task.
4. Read streamed messages and persisted state.
5. Pull the filesystem back out if you need the results locally.
See [Task Lifecycle](/concepts/task-lifecycle).
## What Gets Mounted Into Runtime
When a task runs with a filesystem attached, the runtime sees:
| Path | Purpose |
| --------------- | ---------------------------------- |
| `/workspace` | The mounted filesystem |
| `/root/.claude` | Task-scoped Claude state when used |
| `/root/.codex` | Task-scoped Codex state when used |
Agent code itself is mounted separately and read-only inside the sandbox.
## One Important Distinction
The Python SDK is the public agent runtime surface. The TypeScript and Python clients are for calling deployed agents and managing resources from your app or backend.
Use [Building Agents](/introduction/building-agents) for the runtime model and [Using Agents](/introduction/using-agents) for app-side integration.
# Deployments
Source: https://docs.terminaluse.com/concepts/deployments
Branches, environments, versions, and rollback
TerminalUse deploys agents through branches, environments, and versions.
## The Model
| Primitive | Meaning |
| ------------- | ----------------------------------------------------------- |
| `Environment` | A named deployment policy such as `production` or `preview` |
| `Branch` | A git branch deployment slot |
| `Version` | One concrete deployable build of your agent |
Each deploy creates a new version. A branch points at the currently active version for that branch.
## Environment Resolution
The CLI resolves the effective branch from:
1. `--branch`, if provided
2. the current git branch, if available
3. `main` as a fallback in some flows
That branch is then resolved to an environment by branch rules. For a default setup:
| Branch | Environment |
| --------------- | ------------ |
| `main` | `production` |
| everything else | `preview` |
## Stickiness
`deployment.production.areTasksSticky` and `deployment.preview.areTasksSticky` control what happens to running tasks when a new version activates.
| Setting | Behavior |
| ------- | -------------------------------------------- |
| `true` | Existing tasks stay on their current version |
| `false` | Existing tasks can migrate during rollout |
## Rollback
`tu rollback` rolls a branch back to an earlier version.
Rollback uses the target version's `secrets_snapshot`. Pending environment-secret changes are not rewritten by the rollback itself.
## The Main Operational Commands
* `tu deploy`
* `tu ls`
* `tu rollback`
* `tu logs`
See [Deploying Agents](/introduction/deploying) and [CLI Reference](/api-reference/cli).
# Keys and Secrets
Source: https://docs.terminaluse.com/concepts/keys-and-secrets
API keys, webhook keys, and environment secrets
These three things solve different problems and should not be conflated.
## API Keys
API keys authenticate calls to the TerminalUse API from your app, backend, or automation.
Use API keys when you want to:
* create tasks
* manage agents, filesystems, and projects
* call the SDK from your application
See [API Keys](/api-reference/api-keys).
## Webhook Keys
Webhook keys are for verifying inbound webhook delivery from TerminalUse.
The CLI surface for these is `tu keys`, and it manages webhook keys specifically, not general API auth keys.
See [List Webhook Keys](/api-reference/webhook-keys-list).
## Environment Secrets
Environment secrets are runtime environment variables attached to an agent environment such as `production` or `preview`.
Use them for:
* provider credentials
* database URLs
* external service tokens
Manage them with `tu env add`, `tu env ls`, `tu env get`, `tu env rm`, `tu env pull`, and `tu env import`.
## The Important Distinction
| Surface | Used for |
| ------------------ | ------------------------------------------- |
| API key | Calling TerminalUse APIs |
| Webhook key | Verifying TerminalUse webhooks |
| Environment secret | Supplying runtime config to deployed agents |
# Product Surfaces
Source: https://docs.terminaluse.com/concepts/product-surfaces
Which TerminalUse surface to use for agent code, app code, CLI workflows, and API operations
One of the easiest ways to get confused in TerminalUse is to mix up the product surfaces.
## The Four Main Surfaces
### 1. Agent Runtime Surface
Use this when you are writing the agent itself.
* Python only today
* `terminaluse.lib`
* `AgentServer`
* `TaskContext`
* `adk`
This is the surface for `@server.on_create`, `@server.on_event`, `@server.on_cancel`, runtime filesystem sync, and task-scoped state.
### 2. App SDK Surface
Use this when your backend or application needs to call deployed agents.
* Python SDK: `terminaluse`
* TypeScript SDK: `@terminaluse/sdk`
This is the surface for creating filesystems, creating tasks, sending events, listing messages, and downloading outputs.
### 3. CLI Surface
Use this for local workflows and operations.
* `tu init`
* `tu deploy`
* `tu tasks ...`
* `tu fs ...`
* `tu env ...`
The CLI is not a perfect wrapper around every HTTP endpoint. If a CLI command does not exist, the docs should not invent one.
### 4. HTTP API / OpenAPI Surface
Use this when you are integrating directly at the API level or when you need the generated endpoint reference.
This is the most literal surface. It is not the best place to learn the product model first.
## A Good Default Mental Map
```
Write agent code -> terminaluse.lib
Call deployed agents -> Python SDK / TypeScript SDK
Operate locally -> tu CLI
Inspect raw endpoints -> API reference
```
## Common Mistakes
* Treating the ADK as if it were the app SDK
* Assuming every SDK method also has a CLI command
* Learning the product from raw endpoint pages instead of the concept pages
## Read Next
* [Core Model](/concepts/core-model)
* [Workspaces and Filesystems](/concepts/workspaces-and-filesystems)
* [CLI Reference](/api-reference/cli)
# Sandboxing
Source: https://docs.terminaluse.com/concepts/sandboxing
What the runtime can access inside a task
TerminalUse runs agent handlers inside a bubblewrap sandbox.
## What This Means In Practice
* Agent code is mounted read-only.
* `/workspace` is mounted from the attached filesystem.
* Task system folders such as `/root/.claude` and `/root/.codex` are mounted per task.
* `/tmp` is ephemeral.
* Network access is shared so agents can call model APIs and external services.
## Current Implementation
The current runtime uses bubblewrap, not nsjail.
It creates separate user, PID, IPC, and UTS namespaces, keeps network access shared, bind-mounts `/proc` read-only, and starts from a cleared environment before re-injecting the runtime environment.
## Read-Only Workspace Mode
`/workspace` is not always writable.
If a caller can run a task and read the filesystem but cannot update the filesystem, the runtime mounts `/workspace` read-only for that task.
See [Access Control](/concepts/access-control).
## What The Docs Do Not Promise
* A sandbox wall-clock timeout is not currently enforced at the bubblewrap runner layer.
* CLI approval prompts are not part of TerminalUse itself; approval behavior belongs to the tool you run inside the agent.
# Task Lifecycle
Source: https://docs.terminaluse.com/concepts/task-lifecycle
How tasks are created, routed, and updated
A task is one running unit of work for an agent. Most product integrations map one user session, conversation, or job to one task.
## Lifecycle Hooks
The public Python runtime exposes three handlers:
* `@server.on_create`
* `@server.on_event`
* `@server.on_cancel`
There is no public `on_complete` hook.
## Typical Flow
1. Your app creates a task with `agent_name` or `agent_id`.
2. You optionally attach an existing filesystem with `filesystem_id`, or ask TerminalUse to create one from a `project_id`.
3. TerminalUse resolves the target version for the requested branch.
4. The runtime calls `on_create`.
5. Your app sends follow-up events and the runtime calls `on_event`.
6. The agent emits messages and updates state.
7. If the task is cancelled, the runtime calls `on_cancel`.
## What Lives On A Task
| Resource | Scope |
| ---------------- | --------------------------------------------- |
| Messages | Per task |
| Events | Per task |
| State | Per task and agent |
| System folders | Per task |
| Filesystem mount | Shared only if you attach the same filesystem |
## Filesystem Behavior
If a task has a filesystem, TerminalUse syncs that filesystem into `/workspace` before `on_create` and `on_event`, and syncs changes back after those handlers complete.
`on_cancel` is different: the current runtime registers cancel handlers with sync disabled, so you should not rely on writing files in `on_cancel` and expecting those workspace changes to persist.
If the caller can run the task but does not have filesystem update permission, `/workspace` is mounted read-only. See [Access Control](/concepts/access-control).
## TaskContext
`TaskContext` gives your handler pre-bound helpers for the current task:
* `ctx.task`
* `ctx.agent`
* `ctx.messages`
* `ctx.state`
* `ctx.events`
See [Building Agents](/introduction/building-agents) for examples.
# Workspaces and Filesystems
Source: https://docs.terminaluse.com/concepts/workspaces-and-filesystems
How `/workspace`, persistent filesystems, archive sync, and single-file operations fit together
Filesystems are one of the core TerminalUse primitives. They deserve a concept page, not just endpoint docs.
## The Model
* A filesystem is persistent storage.
* A task can attach a filesystem.
* The task sees that filesystem at `/workspace`.
That means `/workspace` is a runtime mount of a persistent resource, not just a scratch directory.
## Three Different File Flows
### 1. Whole-Filesystem Archive Flow
Use this for directory-level push and pull.
* `tu fs push`
* `tu fs pull`
* `get_upload_url`
* `get_download_url`
These flows move archives, not individual files.
### 2. Single-File Flow
Use this for exact path-level reads and writes.
* `upload_file`
* `download_file`
* `get_file`
* `list_files`
This is the right surface when you want one path, one payload, or conditional write semantics.
### 3. Runtime Sync Flow
Use this inside Python agent code when you need explicit sync during handler execution.
* `adk.filesystem.sync_down`
* `adk.filesystem.sync_up`
* `adk.task.sync_down_system_folder`
* `adk.task.sync_up_system_folder`
## Writable vs Read-Only `/workspace`
`/workspace` is not always writable.
* writable when the caller can update the attached filesystem
* read-only when the caller can run the task but lacks update permission
* tmpfs when no filesystem is attached
## Projects Matter
Filesystems inherit their permission boundary from the parent project. If you are designing a multi-tenant or access-controlled app, project design is part of the filesystem model.
## Read Next
* [List Filesystems](/api-reference/filesystems/list)
* [Filesystem Sync](/advanced/filesystem-sync)
* [Access Control](/concepts/access-control)
# Building Agents
Source: https://docs.terminaluse.com/introduction/building-agents
The public Python runtime model for TerminalUse agents
You build TerminalUse agents with the Python runtime API.
The public runtime surface is `AgentServer`, and it exposes three lifecycle hooks:
* `@server.on_create`
* `@server.on_event`
* `@server.on_cancel`
There is no public `on_complete` hook.
## Start With tu init
```bash theme={"dark"}
tu init
```
That creates an agent project with:
```text theme={"dark"}
my-agent/
├── src/agent.py
├── config.yaml
├── Dockerfile
└── pyproject.toml
```
## Minimal Agent
```python theme={"dark"}
from typing import Any
from terminaluse.lib import AgentServer, TaskContext
from terminaluse.types import Event, TextPart
server = AgentServer()
@server.on_create
async def handle_create(ctx: TaskContext, params: dict[str, Any]):
await ctx.state.create({"turns": 0})
await ctx.messages.send("Task created.")
@server.on_event
async def handle_event(ctx: TaskContext, event: Event):
if not isinstance(event.content, TextPart):
await ctx.messages.send("Only text events are supported.")
return
state = await ctx.state.get() or {"turns": 0}
turns = int(state.get("turns", 0)) + 1
await ctx.state.update({"turns": turns})
await ctx.messages.send(f"turn {turns}: {event.content.text}")
@server.on_cancel
async def handle_cancel(ctx: TaskContext):
await ctx.messages.send("Task cancelled.")
```
## The Handler Model
### on\_create
Runs once when the task is created.
Use it for:
* initializing state
* validating params
* sending the first message
### on\_event
Runs every time your app sends an event to the task.
This is where most agent logic lives.
### on\_cancel
Runs when the task is cancelled.
Use it for cleanup or last-message behavior if needed.
Important runtime detail: `on_cancel` does not run with the normal automatic filesystem sync wrapper. Do not rely on `/workspace` writes made in `on_cancel` being persisted automatically.
## TaskContext
Every handler receives a `TaskContext`.
| Attribute | Purpose |
| -------------- | ------------------------------ |
| `ctx.task` | Current task metadata |
| `ctx.agent` | Current agent metadata |
| `ctx.messages` | Send or list task messages |
| `ctx.state` | Read and update per-task state |
| `ctx.events` | Send or list events |
`TaskContext` is the default path. The lower-level `adk.*` modules are still useful in helper code when you need to pass IDs explicitly.
## Messages
The most common thing you do inside a handler is emit messages:
```python theme={"dark"}
await ctx.messages.send("plain text works")
```
For structured output, send a typed part:
```python theme={"dark"}
from terminaluse.types import DataPart
await ctx.messages.send(DataPart(data={"status": "complete"}))
```
## State
State is persisted per task and agent.
```python theme={"dark"}
await ctx.state.create({"status": "started"})
state = await ctx.state.get() or {}
await ctx.state.update({"status": "done"})
```
Use it for:
* conversation continuity
* IDs or handles you need on future turns
* lightweight workflow progress
## Filesystem Access
If the task has an attached filesystem, your agent sees it at `/workspace`.
TerminalUse syncs that filesystem around `on_create` and `on_event`. If the caller only has read access to the filesystem, `/workspace` is mounted read-only for that task.
`on_cancel` is the exception: the runtime skips the automatic sync wrapper for cancel handlers.
See [Filesystem Sync](/advanced/filesystem-sync) and [Access Control](/concepts/access-control).
## TypeScript vs Python
The TypeScript SDK is for calling deployed agents and managing TerminalUse resources from your application.
The public agent runtime shown on this page is Python.
# Deploying Agents
Source: https://docs.terminaluse.com/introduction/deploying
How deploys resolve to environments, branches, and versions
## The Core Command
```bash theme={"dark"}
tu deploy
```
The CLI reads `config.yaml`, resolves the effective branch, builds an image, and creates a new version for the matching environment.
## The Deployment Model
| Resource | Meaning |
| ------------- | ------------------------------------------------ |
| `Environment` | A named policy such as `production` or `preview` |
| `Branch` | A branch-specific deployment slot |
| `Version` | One deployed build of your agent |
Every deploy creates a new version. A branch points at the current active version.
## Branch Resolution
For `tu deploy`, the branch comes from:
1. `--branch`, if provided
2. the current git branch
3. fallback logic when git context is unavailable
The platform then resolves that branch to an environment using branch rules.
In a default setup:
| Branch | Environment | Config section |
| -------------- | ------------ | ----------------------- |
| `main` | `production` | `deployment.production` |
| other branches | `preview` | `deployment.preview` |
## config.yaml
```yaml theme={"dark"}
agent:
name: my-namespace/my-agent
description: Example agent
sdk_type: claude_agent_sdk
deployment:
production:
replicaCount: 1
areTasksSticky: true
preview:
replicaCount: 1
areTasksSticky: false
```
## Task Stickiness
`areTasksSticky` controls what happens to running tasks during a rollout.
| Setting | Behavior |
| ------- | ------------------------------------------------- |
| `true` | Running tasks stay on the version they started on |
| `false` | Running tasks can migrate during rollout |
If you need to move running tasks later, use `tu tasks migrate`.
## Environment Secrets
Runtime secrets are environment-scoped.
Use `tu env add` to set them and `tu env ls`, `tu env get`, `tu env rm`, `tu env pull`, and `tu env import` to manage them.
Your code reads them through normal environment variables:
```python theme={"dark"}
import os
api_key = os.environ["API_KEY"]
```
## Rollback
Use `tu rollback` to move a branch back to an earlier version.
```bash theme={"dark"}
tu rollback
```
Rollback uses the target version's secret snapshot. Pending environment-secret changes are not rewritten by the rollback.
## Deployment History
Use `tu ls` to inspect version history and branch events:
```bash theme={"dark"}
tu ls
tu ls
```
Use `tu logs` to inspect runtime behavior:
```bash theme={"dark"}
tu logs
tu logs --since 1h --level WARNING
```
## Useful Flags
| Command | Common flags |
| ------------- | ----------------------------------------------------------------------------------------- |
| `tu deploy` | `--config`, `--branch`, `--skip-build`, `--no-cache`, `--yes`, `--verbose`, `--detach` |
| `tu rollback` | `--branch`, `--version`, `--agent`, `--config`, `--yes`, `--json` |
| `tu logs` | `--task`, `--level`, `--source`, `--since`, `--until`, `--version`, `--limit`, `--follow` |
## Remote Builds
Remote build mode is controlled by the CLI runtime, and when enabled it builds remotely instead of locally.
One important constraint: remote builds are `linux/amd64` only.
See [CLI Reference](/api-reference/cli) for command details.
# Frontend Integration
Source: https://docs.terminaluse.com/introduction/frontend-integration
Build chat interfaces with Terminal Use agents using the Vercel AI SDK
## Architecture
```mermaid theme={"dark"}
sequenceDiagram
participant Client as React App (useChat)
participant Server as Next.js API Routes
participant TU as Terminal Use Platform
Client->>Server: POST /api/tasks
Server->>TU: Create task
TU-->>Server: Task ID
Server-->>Client: Task ID
Client->>Server: POST /api/chat
Server->>TU: POST /tasks/{id}/events
TU-->>Server: SSE stream
Server-->>Client: UI message stream
```
The frontend uses the Vercel AI SDK's `useChat` hook. API routes proxy requests to Terminal Use and stream responses back.
## Setup
```bash theme={"dark"}
npm install @terminaluse/vercel-ai-sdk-provider
```
## Creating Tasks
```tsx Client theme={"dark"}
// Create task on first message
const response = await fetch('/api/tasks', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ agentName: AGENT_NAME, filesystemId }),
});
const task = await response.json();
setTaskId(task.id);
```
```typescript Server theme={"dark"}
// app/api/tasks/route.ts
import { TerminalUseClient } from '@terminaluse/sdk';
const client = new TerminalUseClient({
environment: process.env.TERMINALUSE_BASE_URL ?? 'https://api.terminaluse.com',
bearerAuth: { token: process.env.TERMINALUSE_API_KEY! },
});
export async function POST(request: Request) {
const { agentName, filesystemId } = await request.json();
const task = await client.tasks.create({
agent_name: agentName,
filesystem_id: filesystemId,
});
return Response.json(task);
}
```
## Sending Messages
```tsx Client theme={"dark"}
import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport } from 'ai';
const AGENT_NAME = 'my-namespace/my-agent';
// Configure transport with task context
const transport = useMemo(
() => new DefaultChatTransport({
api: '/api/chat',
body: { agentName: AGENT_NAME, taskId },
}),
[taskId]
);
const { messages, sendMessage, status } = useChat({
id: 'chat-session', // Client-generated session ID
transport,
});
// Send message with task context
await sendMessage(
{ text: inputValue },
{ body: { agentName: AGENT_NAME, taskId } }
);
```
```typescript Server theme={"dark"}
// app/api/chat/route.ts
import { streamText, convertToModelMessages, type UIMessage } from 'ai';
import { createTerminalUseProvider } from '@terminaluse/vercel-ai-sdk-provider';
const tu = createTerminalUseProvider({
apiKey: process.env.TERMINALUSE_API_KEY!,
baseURL: process.env.TERMINALUSE_BASE_URL!,
});
export async function POST(req: Request) {
const { messages, agentName, taskId } = await req.json();
const result = streamText({
model: tu.agent(agentName),
messages: await convertToModelMessages(messages as UIMessage[]),
providerOptions: {
terminaluse: { taskId },
},
});
return result.toUIMessageStreamResponse();
}
```
## Loading Message History
When resuming an existing task, fetch and display previous messages.
```tsx Client theme={"dark"}
import { useQuery } from '@tanstack/react-query';
import { useLayoutEffect } from 'react';
function ExistingTaskChat({ taskId }: { taskId: string }) {
const { data: history, isLoading } = useQuery({
queryKey: ['taskMessages', taskId],
queryFn: async () => {
const response = await fetch(`/api/tasks/${taskId}/messages`);
return response.json();
},
});
const { messages, setMessages } = useChat({ id: taskId, transport });
// Initialize with history
useLayoutEffect(() => {
if (history) setMessages(history);
}, [history, setMessages]);
if (isLoading) return
Loading...
;
// ... render chat interface
}
```
```typescript Server theme={"dark"}
// app/api/tasks/[taskId]/messages/route.ts
import { TerminalUseClient } from '@terminaluse/sdk';
const client = new TerminalUseClient({
environment: process.env.TERMINALUSE_BASE_URL ?? 'https://api.terminaluse.com',
bearerAuth: { token: process.env.TERMINALUSE_API_KEY! },
});
export async function GET(
request: Request,
{ params }: { params: Promise<{ taskId: string }> }
) {
const { taskId } = await params;
const response = await client.messagesV2.list({
task_id: taskId,
direction: 'newer',
});
return Response.json(response.data);
}
```
## Handling Message Parts
The following is a sample implementation. See the [Vercel AI SDK documentation](https://sdk.vercel.ai/docs/ai-sdk-ui/chatbot#message-parts) for more details on displaying UIMessage parts.
```tsx theme={"dark"}
import { isTextUIPart, isReasoningUIPart, isToolUIPart } from 'ai';
import type { UIMessage, ToolUIPart } from 'ai';
function MessagePart({ part }: { part: UIMessage['parts'][number] }) {
if (isTextUIPart(part)) {
return
{part.text}
;
}
if (isReasoningUIPart(part)) {
return (
Reasoning: {part.text}
);
}
if (isToolUIPart(part)) {
const toolPart = part as ToolUIPart;
return (
{toolPart.toolName}
{JSON.stringify(toolPart.input, null, 2)}
{toolPart.output && (
{JSON.stringify(toolPart.output, null, 2)}
)}
);
}
return null;
}
```
## Environment Variables
```bash theme={"dark"}
TERMINALUSE_API_KEY=your_api_key
TERMINALUSE_BASE_URL=https://api.terminaluse.com # Optional, defaults to production
```
| Variable | Required | Default | Description |
| ---------------------- | -------- | ----------------------------- | -------------------------------------------------- |
| `TERMINALUSE_API_KEY` | Yes | — | Your API key from the platform |
| `TERMINALUSE_BASE_URL` | No | `https://api.terminaluse.com` | API base URL (override for self-hosted or staging) |
# Quick Start
Source: https://docs.terminaluse.com/introduction/quickstart
Create, deploy, and run your first TerminalUse agent
This quickstart follows one path end to end:
* install the CLI
* scaffold an agent
* deploy it
* create a project and task
* pull the task output back to your machine
The point of TerminalUse is that you do not have to bolt together your own deploy flow, sandbox, task state, streaming, and filesystem transfer. You write the agent, deploy it, and get a persistent `/workspace` plus a task API on top.
```bash theme={"dark"}
uv tool install terminaluse
```
If `tu` is not found, run `uv tool update-shell` and restart your shell once.
```bash theme={"dark"}
tu login
```
```bash theme={"dark"}
tu init
```
`tu init` creates a Python agent project with `config.yaml`, `Dockerfile`, and `src/agent.py`.
```python theme={"dark"}
from typing import Any
from terminaluse.lib import AgentServer, TaskContext
from terminaluse.types import Event, TextPart
server = AgentServer()
@server.on_create
async def handle_create(ctx: TaskContext, params: dict[str, Any]):
await ctx.state.create({"turns": 0})
await ctx.messages.send("Task created. Send me a message and I will write it to /workspace/output.txt.")
@server.on_event
async def handle_event(ctx: TaskContext, event: Event):
if not isinstance(event.content, TextPart):
await ctx.messages.send("Only text events are supported in this quickstart.")
return
state = await ctx.state.get() or {"turns": 0}
turns = int(state.get("turns", 0)) + 1
await ctx.state.update({"turns": turns})
with open("/workspace/output.txt", "a") as f:
f.write(f"turn {turns}: {event.content.text}\n")
await ctx.messages.send(f"Wrote turn {turns} to /workspace/output.txt")
@server.on_cancel
async def handle_cancel(ctx: TaskContext):
await ctx.messages.send("Task cancelled.")
```
```bash theme={"dark"}
tu deploy -y
```
```bash theme={"dark"}
tu projects create --namespace --name quickstart
```
Copy the returned project ID.
```bash theme={"dark"}
tu tasks create \
-a / \
-p \
-m "hello from the quickstart"
```
Using `-p` auto-creates a filesystem for the task. If you already have a filesystem, use `-f ` instead.
```bash theme={"dark"}
tu tasks send -m "write one more line"
```
```bash theme={"dark"}
tu tasks pull --out ./quickstart-output
```
Your task filesystem will be under `./quickstart-output/workspace`.
## What You Just Used
| Command | Purpose |
| -------------------- | ------------------------------------------------- |
| `tu init` | Scaffold an agent project |
| `tu deploy` | Build and deploy a new version |
| `tu projects create` | Create a permission boundary for filesystems |
| `tu tasks create` | Start a task |
| `tu tasks send` | Send another event to an existing task |
| `tu tasks pull` | Download the task's filesystem and system folders |
## Next Steps
* Runtime model: [Building Agents](/introduction/building-agents)
* App integration: [Using Agents](/introduction/using-agents)
* Deployment model: [Deploying Agents](/introduction/deploying)
# Using Agents
Source: https://docs.terminaluse.com/introduction/using-agents
Call deployed agents from your app with the SDKs
This page covers the app-side integration path:
* create a filesystem
* create a task
* send events
* inspect messages and files
## Client Setup
```typescript TypeScript theme={"dark"}
import { TerminalUseClient } from '@terminaluse/sdk';
const client = new TerminalUseClient({
environment: process.env.TERMINALUSE_BASE_URL ?? 'https://api.terminaluse.com',
bearerAuth: { token: process.env.TERMINALUSE_API_KEY! },
});
```
```python Python theme={"dark"}
from terminaluse import AsyncTerminalUse
client = AsyncTerminalUse(token="your_api_key")
```
## Create A Filesystem
Filesystems belong to projects. Projects are the permission boundary; filesystems are the storage.
```typescript TypeScript theme={"dark"}
const project = await client.projects.create({
namespace_id: 'ns_123',
name: 'my-project'
});
const filesystem = await client.filesystems.create({
project_id: project.id,
name: 'customer-123'
});
```
```python Python theme={"dark"}
project = await client.projects.create(
namespace_id="ns_123",
name="my-project"
)
filesystem = await client.filesystems.create(
project_id=project.id,
name="customer-123"
)
```
## Put Files Into It
There are three different file flows:
| Use case | Surface |
| ------------------------------------------------ | ---------------------------------------------------------------- |
| Upload or download the entire filesystem archive | `tu fs push` / `tu fs pull` or `getUploadUrl` / `getDownloadUrl` |
| Upload or download one file | `uploadFile` / `download_file` / `downloadFile` |
| Sync `/workspace` inside the running agent | Python ADK `sync_down` / `sync_up` |
See [Workspaces and Filesystems](/concepts/workspaces-and-filesystems) for the full breakdown.
## Create A Task
Attach either an existing filesystem or a project:
```typescript TypeScript theme={"dark"}
const task = await client.tasks.create({
agent_name: 'my-namespace/my-agent',
filesystem_id: filesystem.id,
params: {
goal: 'summarize this repo'
}
});
```
```python Python theme={"dark"}
task = await client.tasks.create(
agent_name="my-namespace/my-agent",
filesystem_id=filesystem.id,
params={
"goal": "summarize this repo"
},
)
```
If you pass `project_id` instead of `filesystem_id`, TerminalUse creates a filesystem for the task automatically.
## Send Events
```typescript TypeScript theme={"dark"}
await client.tasks.sendEvent({
task_id: task.id,
content: {
type: 'text',
text: 'please analyze the files'
}
});
```
```python Python theme={"dark"}
from terminaluse.types import TextPart
await client.tasks.send_event(
task_id=task.id,
content=TextPart(text="please analyze the files"),
)
```
## Inspect Files
### File Metadata And Content
```typescript TypeScript theme={"dark"}
const files = await client.filesystems.listFiles({
filesystem_id: filesystem.id,
recursive: true
});
const file = await client.filesystems.getFile({
filesystem_id: filesystem.id,
file_path: 'output/report.md',
include_content: true
});
```
```python Python theme={"dark"}
files = await client.filesystems.list_files(
filesystem_id=filesystem.id,
recursive=True,
)
file = await client.filesystems.get_file(
filesystem_id=filesystem.id,
file_path="output/report.md",
include_content=True,
)
```
### Single-File Download
```typescript TypeScript theme={"dark"}
const response = await client.filesystems.downloadFile({
filesystem_id: filesystem.id,
path: 'output/report.md'
});
```
```python Python theme={"dark"}
chunks = client.filesystems.download_file(
filesystem_id=filesystem.id,
path="output/report.md",
)
```
For whole-filesystem transfers, use `tu fs push`, `tu fs pull`, `getUploadUrl`, or `getDownloadUrl` instead.
# What is TerminalUse?
Source: https://docs.terminaluse.com/introduction/what-is-terminaluse
A platform for deploying agents that need tasks, state, and filesystems
TerminalUse is a deployment platform for agents that need three things at the same time:
* long-lived task state
* persistent files at `/workspace`
* production deployment primitives like versions, rollback, logs, and secrets
## Why TerminalUse
Without TerminalUse, teams usually have to stitch together several separate pieces:
* packaging and deploying the agent
* running it in a sandboxed environment
* streaming messages back to users
* persisting state across turns
* moving files in and out of the workspace
TerminalUse brings those pieces together behind one runtime model and one deploy flow, while keeping filesystems as first-class resources separate from task lifetime.
## The Product Model
If you understand these nouns, the rest of the docs become much easier:
| Primitive | Role |
| ------------- | --------------------------------------------------- |
| `Agent` | Your deployed runtime |
| `Task` | One conversation or job running against an agent |
| `Filesystem` | Persistent storage mounted into a task |
| `Project` | Permission boundary for filesystems |
| `Namespace` | Isolation boundary for compute and storage |
| `Environment` | Deployment policy such as `production` or `preview` |
| `Branch` | A branch-specific deployment slot |
| `Version` | One deployable build |
See [Core Model](/concepts/core-model) for the full mental model.
## The Two Main Loops
### Build And Deploy
1. Write agent code in Python.
2. Run `tu deploy`.
3. TerminalUse creates a new version and activates it on the resolved branch.
### Run And Interact
1. Create or choose a filesystem.
2. Create a task for an agent.
3. Send events to that task.
4. Read messages, update state, and inspect files written to `/workspace`.
## What The Platform Handles
* Task routing and lifecycle
* Filesystem mount and sync
* Task-scoped system folders such as `/root/.claude` and `/root/.codex`
* Versioning, rollback, and logs
* Environment secret injection
* Runtime sandboxing around your handler execution
## Public Surfaces
TerminalUse has two distinct developer surfaces:
* The Python runtime API, which you use to build agents with `AgentServer`
* The Python and TypeScript client SDKs, which you use to call deployed agents and manage resources from your app
That distinction matters because not every surface has feature parity. If a CLI, Python, or TypeScript example is shown in these docs, it should reflect a real supported surface.
## Where To Start
* New to TerminalUse: [Quick Start](/introduction/quickstart)
* Writing the agent runtime: [Building Agents](/introduction/building-agents)
* Integrating from your app: [Using Agents](/introduction/using-agents)
* Understanding rollout behavior: [Deployments](/concepts/deployments)