# 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 Terminal Use 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 openapi.json DELETE /agents/{agent_id} Delete an agent by its unique ID. # Delete Agent by Name Source: https://docs.terminaluse.com/api-reference/agents/delete-by-name openapi.json DELETE /agents/name/{namespace_slug}/{agent_name} Delete an agent by namespace slug and agent name. # Deploy Agent Source: https://docs.terminaluse.com/api-reference/agents/deploy openapi.json POST /agents/deploy Deploy an agent to the platform. Called by CLI after pushing the container image to the registry. Creates or updates agent, branch, and version records. The deployment is asynchronous - poll GET /branches/{branch_id} for status updates until status is READY or FAILED. **Flow:** 1. CLI builds and pushes image to registry (using /registry/auth) 2. CLI calls POST /agents/deploy with image details 3. Platform creates records and triggers K8s deployment 4. Container starts and calls POST /versions/register 5. CLI polls GET /branches/{id} for status 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 openapi.json GET /agents List all registered agents, optionally filtered by query parameters. # Get Agent Source: https://docs.terminaluse.com/api-reference/agents/retrieve openapi.json GET /agents/{agent_id} Get an agent by its unique ID. # Get Agent by Name Source: https://docs.terminaluse.com/api-reference/agents/retrieve-by-name openapi.json GET /agents/name/{namespace_slug}/{agent_name} Get an agent by namespace slug and agent name. # Create API Key Source: https://docs.terminaluse.com/api-reference/api-keys/create openapi.json POST /api_keys Create a new org-level API key. Only org admins can create 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 openapi.json GET /api_keys/{api_key_id} Get details of a specific API key. Only org admins can view keys. # List API Keys Source: https://docs.terminaluse.com/api-reference/api-keys/list openapi.json GET /api_keys List all API keys for the organization. Only org admins can list keys. # Revoke API Key Source: https://docs.terminaluse.com/api-reference/api-keys/revoke openapi.json DELETE /api_keys/{api_key_id} Revoke an API key. Only org admins can revoke keys. 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 openapi.json PATCH /api_keys/{api_key_id} Update scopes for an API key. Only org admins can update key scopes. 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 openapi.json PATCH /api_keys/{api_key_id}/sharing-groups Update default sharing groups for resources created by an API key. 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 openapi.json GET /branches/{branch_id} Get branch details by ID. Used by CLI to poll for branch status after calling POST /agents/deploy. Poll until status is READY (success) or FAILED/TIMEOUT (failure). # List Branches Source: https://docs.terminaluse.com/api-reference/branches-list openapi.json GET /agents/{namespace_slug}/{agent_name}/branches List all branches for an agent. Each branch represents a git branch (e.g., main, staging, feature-x). By default, retired branches are excluded. # List Branch Events Source: https://docs.terminaluse.com/api-reference/branches-list-events openapi.json GET /branches/{branch_id}/events List lifecycle events across all versions for a branch. # List Environment Branches Source: https://docs.terminaluse.com/api-reference/branches-list-for-environment openapi.json GET /agents/{namespace_slug}/{agent_name}/environments/{env_name}/branches List all branches that match this environment's branch rules. For wildcard environments (like preview with ["*"]), returns all non-retired branches for the agent. For specific branch rules (like production with ["main"]), returns only the matching branch(es). Branches are sorted by updated_at descending (most recent first). # List Branch Versions Source: https://docs.terminaluse.com/api-reference/branches-list-versions openapi.json GET /branches/{branch_id}/versions List all versions for a branch. Versions are ordered by deployed_at descending (most recent first). Each version represents a git commit that was deployed. # Redeploy Branch Source: https://docs.terminaluse.com/api-reference/branches-redeploy openapi.json POST /branches/{branch_id}/redeploy Redeploy a branch with the current secrets from the EnvVar table. Creates a new Version with: - Same image as current version - Fresh secrets_snapshot from current EnvVars Use this after updating secrets to apply them to a running branch. # Rollback Branch Source: https://docs.terminaluse.com/api-reference/branches-rollback openapi.json POST /branches/{branch_id}/rollback Roll back a branch to a previous version by branch ID. Key behavior: - EnvVar table is NOT modified (remains pending state) - Uses Version.secrets_snapshot as deployed state # Rollback Named Branch Source: https://docs.terminaluse.com/api-reference/branches-rollback-by-name openapi.json POST /agents/{namespace_slug}/{agent_name}/branches/{branch}/rollback Roll back a branch to a previous version by namespace, agent, and git branch name. Key behavior: - EnvVar table is NOT modified (remains pending state) - Uses Version.secrets_snapshot as deployed state # 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 Terminal Use | | `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 openapi.json GET /events/{event_id} # List Events Source: https://docs.terminaluse.com/api-reference/events-list openapi.json GET /events List events for a specific task and agent. Optionally filter for events after a specific sequence ID. Results are ordered by sequence_id. # Create Filesystem Source: https://docs.terminaluse.com/api-reference/filesystems/create openapi.json POST /filesystems Create a new filesystem linked to a project. 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 openapi.json GET /filesystems/{filesystem_id}/file-download Download a single file from the filesystem. 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 openapi.json POST /filesystems/{filesystem_id}/download-url Get a presigned URL for direct download from GCS. 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 openapi.json GET /filesystems/{filesystem_id}/files/{file_path} Retrieve a specific file (or directory) from the filesystem manifest. Retrieve a specific file (or directory) from the filesystem manifest. # Get Upload URL Source: https://docs.terminaluse.com/api-reference/filesystems/get-upload-url openapi.json POST /filesystems/{filesystem_id}/upload-url Get a presigned URL for direct upload to GCS. 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 Terminal Use 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 openapi.json GET /filesystems List all filesystems accessible to the current user. ## 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 openapi.json GET /filesystems/{filesystem_id}/files List files in the filesystem manifest with optional filtering. 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 openapi.json GET /filesystems/{filesystem_id} Get filesystem details by 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 openapi.json POST /filesystems/{filesystem_id}/sync-complete Notify that a sync operation completed and provide the file manifest. 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 openapi.json PUT /filesystems/{filesystem_id}/file-upload Upload a single file into a filesystem by relative path. Defaults to overwrite mode; conditional mode can be used for strict concurrency control. 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 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 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 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 openapi.json POST /groups Create an org-owned access group. # Delete Group Source: https://docs.terminaluse.com/api-reference/groups/delete openapi.json DELETE /groups/{group_id} Delete an access group. 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 openapi.json GET /groups/{group_id} Get group details. # List Groups Source: https://docs.terminaluse.com/api-reference/groups/list openapi.json GET /groups List groups visible to the current principal. # List Group Managers Source: https://docs.terminaluse.com/api-reference/groups/list-managers openapi.json GET /groups/{group_id}/managers # List Group Members Source: https://docs.terminaluse.com/api-reference/groups/list-members openapi.json GET /groups/{group_id}/members # List Group Visible Orgs Source: https://docs.terminaluse.com/api-reference/groups/list-visible-orgs openapi.json GET /groups/{group_id}/visible-orgs # Remove Group Manager Source: https://docs.terminaluse.com/api-reference/groups/remove-manager openapi.json DELETE /groups/{group_id}/managers/{member_id} # Remove Group Member Source: https://docs.terminaluse.com/api-reference/groups/remove-member openapi.json DELETE /groups/{group_id}/members/{member_id} # Remove Group Visible Org Source: https://docs.terminaluse.com/api-reference/groups/remove-visible-org 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 openapi.json PATCH /groups/{group_id} Update group metadata. # Get Message Source: https://docs.terminaluse.com/api-reference/messages-get openapi.json GET /v2/messages/{message_id} Get a single message by ID in UIMessage format. Returns the transformed UIMessage or null if not found. # List Messages Source: https://docs.terminaluse.com/api-reference/messages-list openapi.json GET /v2/messages List messages for a task in Vercel AI SDK UIMessage format. This endpoint returns messages transformed from raw SDK events into a normalized UIMessage format suitable for frontend consumption. Features: - Tool merging: tool_use and tool_result are combined into single tool parts - Complete-units pagination: Never splits tool request/result pairs across pages - Runtime transformation: Raw SDK messages are transformed on-demand Args: task_id: The task ID to filter messages by limit: Maximum number of complete messages to return (default: 50) cursor: Opaque cursor string for pagination direction: Pagination direction - "older" (default) or "newer" parent_tool_use_id: Filter messages by parent tool use ID (for subagent context) Returns: ListMessagesV2Response with UIMessages and pagination info # Check Slug Availability Source: https://docs.terminaluse.com/api-reference/namespaces/check-slug-availability openapi.json GET /namespaces/slug-availability/{slug} Check if a namespace slug is available for use. 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 openapi.json POST /namespaces Create a new namespace for multi-tenant isolation. 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 openapi.json DELETE /namespaces/{namespace_id} Delete a namespace (must be empty). 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 openapi.json GET /namespaces/{namespace_id} Get a namespace by ID. # Get Namespace by Slug Source: https://docs.terminaluse.com/api-reference/namespaces/get-by-slug openapi.json GET /namespaces/slug/{slug} Get a namespace by its slug. # List Namespaces Source: https://docs.terminaluse.com/api-reference/namespaces/list openapi.json GET /namespaces List all namespaces the user has access to, optionally filtered by org. # Retry Namespace Provisioning Source: https://docs.terminaluse.com/api-reference/namespaces/retry-provisioning openapi.json POST /namespaces/{namespace_id}/retry-provisioning Retry provisioning for a namespace that failed or is stuck. 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 openapi.json PATCH /namespaces/{namespace_id} Update a namespace's properties. # Create Project Source: https://docs.terminaluse.com/api-reference/projects/create openapi.json POST /projects Create a new project within a namespace. 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 openapi.json DELETE /projects/{project_id} Delete a project (must be empty). 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 openapi.json GET /projects/{project_id} Get a project by ID. # List Projects Source: https://docs.terminaluse.com/api-reference/projects/list openapi.json GET /projects List all projects the user has access to. # List Project Collaborators Source: https://docs.terminaluse.com/api-reference/projects/list-collaborators openapi.json GET /projects/{project_id}/collaborators List explicit collaborator roles on a project. 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 openapi.json DELETE /projects/{project_id}/collaborators/{member_id} Remove explicit collaborator role tuples from a project. 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 openapi.json PUT /projects/{project_id}/collaborators/{member_id} Create or update an explicit collaborator role on a project. 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 openapi.json PATCH /projects/{project_id} Update a project's properties. # Create State Source: https://docs.terminaluse.com/api-reference/states-create openapi.json POST /states # Delete State Source: https://docs.terminaluse.com/api-reference/states-delete openapi.json DELETE /states/{state_id} # Get State Source: https://docs.terminaluse.com/api-reference/states-get openapi.json GET /states/{state_id} Get a state by its unique state ID. # List States Source: https://docs.terminaluse.com/api-reference/states-list openapi.json GET /states List all states, filtered by task_id or agent_id (at least one required). # Update State Source: https://docs.terminaluse.com/api-reference/states-update openapi.json PUT /states/{state_id} # Cancel Task Source: https://docs.terminaluse.com/api-reference/tasks/cancel openapi.json POST /tasks/{task_id}/cancel Cancel a running task. **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 openapi.json POST /tasks Create a new task for an agent. 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 openapi.json DELETE /tasks/{task_id} Delete a task by its unique ID. # List Tasks Source: https://docs.terminaluse.com/api-reference/tasks/list openapi.json GET /tasks List all tasks. # Migrate Tasks Source: https://docs.terminaluse.com/api-reference/tasks/migrate openapi.json POST /tasks/migrate Migrate tasks between versions within the same branch. Supports two modes: bulk migration by source version (from_version_id), or migration of specific tasks (task_ids). Target can be explicit (to_version_id) or latest active version (to_latest). 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 openapi.json GET /tasks/{task_id} Get a task by its unique ID. # Send Event Source: https://docs.terminaluse.com/api-reference/tasks/send-event openapi.json POST /tasks/{task_id}/events Send an event to a running task. ## 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 openapi.json GET /tasks/{task_id}/stream Stream events for a task by its unique ID using Server-Sent Events (SSE). Supports automatic reconnection with Last-Event-ID header. When the client reconnects, it sends the Last-Event-ID header with the last received event ID, and the server resumes from that point. Returns TextStreamPart events (Vercel AI SDK compatible format). **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 openapi.json PUT /tasks/{task_id} Update mutable fields for a task by its unique ID. # Get Version Source: https://docs.terminaluse.com/api-reference/versions-get openapi.json GET /versions/{version_id} Get version details by ID. # List Versions Source: https://docs.terminaluse.com/api-reference/versions-list openapi.json GET /agents/{namespace_slug}/{agent_name}/versions List all versions for an agent. Optionally filter by branch name or version status. Versions are ordered by deployed_at descending (most recent first). # Create Webhook Key Source: https://docs.terminaluse.com/api-reference/webhook-keys-create openapi.json POST /agent_api_keys # Delete Webhook Key Source: https://docs.terminaluse.com/api-reference/webhook-keys-delete openapi.json DELETE /agent_api_keys/{id} Delete API key by ID. # Delete Webhook Key By Name Source: https://docs.terminaluse.com/api-reference/webhook-keys-delete-by-name openapi.json DELETE /agent_api_keys/name/{api_key_name} Delete API key by name. # Get Webhook Key Source: https://docs.terminaluse.com/api-reference/webhook-keys-get openapi.json GET /agent_api_keys/{id} Return API key by ID. # Get Webhook Key By Name Source: https://docs.terminaluse.com/api-reference/webhook-keys-get-by-name openapi.json GET /agent_api_keys/name/{name} Return named API key for the agent ID. # List Webhook Keys Source: https://docs.terminaluse.com/api-reference/webhook-keys-list openapi.json GET /agent_api_keys List API keys for an agent ID. # Access Control Source: https://docs.terminaluse.com/concepts/access-control How agent access, filesystem access, and task access interact Terminal Use access control matters most when you understand tasks and filesystems together. ## The Important Rule Tasks have dual-parent authorization. To access a task, Terminal Use 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, Terminal Use 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 Terminal Use Terminal Use 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. Terminal Use 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 Terminal Use 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 Terminal Use 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 Terminal Use. 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 Terminal Use APIs | | Webhook key | Verifying Terminal Use webhooks | | Environment secret | Supplying runtime config to deployed agents | # Product Surfaces Source: https://docs.terminaluse.com/concepts/product-surfaces Which Terminal Use surface to use for agent code, app code, CLI workflows, and API operations One of the easiest ways to get confused in Terminal Use 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 Terminal Use 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 Terminal Use 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 Terminal Use to create one from a `project_id`. 3. Terminal Use 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, Terminal Use 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 Terminal Use 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 Terminal Use agents You build Terminal Use 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`. Terminal Use 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 Terminal Use 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 Terminal Use 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 Terminal Use 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`, Terminal Use 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 Terminal Use? Source: https://docs.terminaluse.com/introduction/what-is-terminaluse A platform for deploying agents that need tasks, state, and filesystems Terminal Use 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 Terminal Use Without Terminal Use, 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 Terminal Use 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. Terminal Use 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 Terminal Use 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 Terminal Use: [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)