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