Source: /
# Tanomis documentation
Run AI agents as a governed service. You write the agent; Tanomis packages it, runs it per tenant, and gives every action an identity, a policy check, and a record.
Run AI agents as a governed service.
You write the agent in Python. Tanomis packages it, runs it for each of your customers in its own
sandbox, and gives every action an identity, a policy check, and a record. Warm starts, approvals,
and audit come with the platform, not with your code.
[Deploy your first agent](/getting-started/quickstart)
about 10 minutes
[Tour the Console](/getting-started/console-tour)
```bash title="Four commands, one running agent"
tanomis login
tanomis agent new support-copilot --harness claude-agents-sdk
tanomis agent deploy ./agents/support-copilot --tenant northwind-sandbox
tanomis session run support-copilot --prompt "Draft a reply to ticket 4821."
```
- Sandbox per session
- Warm start under a second
- Signed, immutable builds
- Tamper-evident audit ledger
- Your tenants, your policy
## How it works
Your source becomes an immutable **version**. A tenant is **enabled** for it. Your application opens
a **session** and starts **runs**. Every run leaves **evidence**. Nothing skips a step, and nothing
routes traffic until you enable it.
1. **Source** Your Python agent, in your repository.
2. **Version** An immutable build plus the contract you declared.
3. **Enablement** Which version each tenant serves, canary or baseline.
4. **Session** A conversation or unit of work, warm-started in under a second.
5. **Run** One request, streamed, pausable for a person.
6. **Evidence** Who asked, what was allowed, what happened, signed.
## What teams build on it
Three agents that use most of the platform. Each one starts from a guide you can follow today.
Customer-facing
### Support copilot
Reads the ticket and your knowledge base through a governed connector, drafts the reply, and pauses for a person before issuing a refund.
- MCP connector
- Approval on refund
- Streaming
[Build it](/getting-started/quickstart)
Back office
### Invoice reconciler
Picks up a unit of durable work, matches it against the ledger over several sessions, and settles it once. No person ends it by hand.
- Durable work
- Session files
- Egress allowlist
[Build it](/guides/run/durable-work-and-settlement)
Engineering
### Code review assistant
Reads a pull request through the GitHub connector, hands the security pass to a second agent under a narrower grant, and posts one review.
- Delegation
- Schema pins
- Canary rollout
[Build it](/guides/run/delegate-to-another-agent)
## Everything the platform gives you
The feature set, in the order you meet it.
**Build** your code, your model
[Harnesses and models](/guides/build/choose-a-harness) Claude Agent SDK or Deep Agents; any model on the tenant's allowlist, routed through one gateway.
[Governed tools](/guides/connect-tools/mcp-connectors) MCP connectors with an explicit allowlist. Agents never launch servers themselves.
[One manifest](/guides/build/manifest-essentials) Models, tools, hosts, limits, and approvals declared once in agent.yaml.
**Ship** immutable, per tenant
[Versions and canary](/guides/deploy/rollout-and-rollback) One command builds, registers, snapshots. Roll out by tenant share; roll back by re-enabling.
[Warm starts](/concepts/agents-and-versions) A golden snapshot restores the harness in under a second, with no tenant data inside.
[CI and automation](/guides/deploy/deploy-from-ci) Dry-run on every pull request; deploy with an organization-owned credential.
**Run** from your product
[Sessions and streaming](/guides/run/invoke-from-your-application) HTTP in, events out. Reconnect and replay safely; attach files with integrity checks.
[Approvals and questions](/guides/run/approval-required-actions) The run pauses, survives disconnects, and resumes on the first answer, from the Console or your app.
[Delegation](/guides/run/delegate-to-another-agent) One agent hands work to another under a live grant that only narrows.
**Trust** what auditors ask for
[Policy ceilings](/guides/govern/restrict-network-egress) Network egress, model, and tool ceilings per tenant; the manifest can only ask for less.
[Evaluation](/guides/evaluate/overview) Trace every run, score a candidate against the baseline, gate promotion on the verdict.
[Audit ledger](/guides/audit/audit-and-evidence) Append-only, hash-chained, signed per tenant. Export an auditor package in one step.
## Every run is governed
Whatever the agent does, the same six things happen around it. The example below follows one
run end to end: a support copilot answering a ticket for the customer bluewater-bank, that needed
a refund approved before it could finish.
Read the whole model in [Policy, approvals, and evidence](/concepts/governance).
## Start where you are
Building an agent
[Quickstart](/getting-started/quickstart)
Then [Manifest essentials](/guides/build/manifest-essentials) and [MCP connectors](/guides/connect-tools/mcp-connectors).
Integrating an application
[Invoke from your application](/guides/run/invoke-from-your-application)
Then [Sessions and runs](/concepts/sessions-and-runs) and the [Platform API](/api).
Administering a workspace
[Tour the Console](/getting-started/console-tour)
Then [Members and access](/guides/manage/organization-members) and [Audit and evidence](/guides/audit/audit-and-evidence).
Working with a coding agent such as Claude Code, Codex, or Cursor? One command installs skills
that teach it to operate Tanomis and registers a documentation MCP server: see
[Build with a coding agent](/getting-started/coding-agents).
## Reference
[ **CLI** Every tanomis command and flag.](/cli)
[ **agent.yaml** Manifest fields and defaults.](/reference/agent-manifest)
[ **Platform API** Endpoints, headers, errors.](/api)
[ **Events** Run event types and replay.](/guides/run/stream-and-replay-events)
---
Source: /api
# Platform API
The application-facing HTTP surface for agents that are already deployed and enabled for the tenant selected by your workspace.
The application-facing HTTP surface for agents that are already deployed and enabled for the tenant selected by your workspace. Use it when your product or backend needs to invoke an agent directly. Routes used by the Tanomis control plane, an agent sandbox, or a platform operator are not part of this reference. For machine authentication, see [Authenticate a backend](/guides/run/authenticate-your-backend). Human owner/admin operations for credentials and tenants are documented in the [Organization management API](/reference/organization-management-api).
Start with [Sessions and runs](/concepts/sessions-and-runs) when choosing an invocation boundary or designing retry and recovery behavior; return here for exact requests and responses. Use `tanomis` to author, deploy, and roll out agents.
Generated from the Platform API specification on 2026-09-04.
## Request basics
The hosted API origin is:
```text
https://api.tanomis.com
```
Keep the origin free of a trailing `/v1`; every endpoint includes its own version prefix. For another Tanomis environment, use the API origin provided by its administrator.
### Headers
| Header | Required | Description |
|---|---|---|
| `Authorization` | yes | `Bearer` plus a short-lived OAuth access token issued for the Tanomis API audience. Backends obtain tokens with an [organization automation client](/guides/run/authenticate-your-backend); the CLI's human login is not an application credential. |
| `X-Organization-ID` | yes | The `organization_id` from the workspace projection you selected in `GET /v1/me/workspaces`. |
| `X-Workspace-Tenant-ID` | yes | The nested `tenant_id` from the same projection. A header cannot widen what the token grants. Some single-workspace or self-hosted environments take `X-Tenant-ID` instead, as supplied by their administrator. |
> **Authentication:** a backend authenticates with an organization-owned [automation client](/guides/deploy/automation-credentials): exchange its client ID and secret for a short-lived token as described in [Authenticate a backend](/guides/run/authenticate-your-backend). The CLI's human login is not an application credential, and there is no separate token endpoint to guess from these examples.
Selecting a workspace projection supplies request context; the workspace does not own the tenant or its resources. A tenant header cannot widen a customer token. Do not use it to try to switch to a tenant the token does not grant.
### Your first call
With a token in hand, three requests take you from nothing to a running agent:
```text title="1. Discover the workspace"
GET /v1/me/workspaces
Authorization: Bearer
```
```text title="2. Open a session"
POST /v1/sessions
Authorization: Bearer
X-Organization-ID:
X-Workspace-Tenant-ID:
{ "agent_name": "support-copilot" }
```
```text title="3. Start a run and follow it"
POST /v1/sessions/{session_id}/runs
{ "input": { "prompt": "Draft a reply to ticket 4821." }, "idempotency_key": "ticket-4821-1" }
GET /v1/sessions/{session_id}/runs/{run_id}/events
```
The walkthrough with responses is [Invoke from your application](/guides/run/invoke-from-your-application).
### Data conventions
- Request and response bodies are JSON unless an endpoint says otherwise.
- Unknown request fields are rejected instead of ignored.
- Resource IDs are opaque strings. Store them exactly as returned.
- Times use RFC 3339 UTC strings, such as `2026-09-02T11:42:18Z`.
- Optional response fields may be absent. Clients should ignore response fields they do not know.
## Endpoints
### Workspaces
| Method | Path | Operation |
|---|---|---|
| `GET` | `/v1/whoami` | [Inspect the caller](/api/workspaces/whoami) |
| `GET` | `/v1/me/workspaces` | [List workspaces](/api/workspaces/list-workspaces) |
### Agents
| Method | Path | Operation |
|---|---|---|
| `GET` | `/v1/agents` | [List agents](/api/agents/list-agents) |
| `POST` | `/v1/agents/{agent_name}/runs` | [Invoke once and stream](/api/agents/invoke-agent) |
### Sessions
| Method | Path | Operation |
|---|---|---|
| `POST` | `/v1/sessions` | [Create a session](/api/sessions/create-session) |
| `GET` | `/v1/sessions` | [List sessions](/api/sessions/list-sessions) |
| `GET` | `/v1/sessions/{session_id}` | [Read a session](/api/sessions/get-session) |
| `POST` | `/v1/sessions/{session_id}/stop` | [Stop a session](/api/sessions/stop-session) |
| `POST` | `/v1/sessions/{session_id}/resume` | [Resume a session](/api/sessions/resume-session) |
### Runs
| Method | Path | Operation |
|---|---|---|
| `POST` | `/v1/sessions/{session_id}/runs` | [Start a run](/api/runs/start-run) |
| `GET` | `/v1/sessions/{session_id}/runs` | [List runs](/api/runs/list-runs) |
| `GET` | `/v1/sessions/{session_id}/runs/{run_id}` | [Read a run](/api/runs/get-run) |
| `GET` | `/v1/sessions/{session_id}/runs/{run_id}/events` | [Stream run events](/api/runs/stream-run-events) |
| `POST` | `/v1/sessions/{session_id}/runs/{run_id}/cancel` | [Cancel a run](/api/runs/cancel-run) |
### Interactions
| Method | Path | Operation |
|---|---|---|
| `GET` | `/v1/sessions/{session_id}/interactions` | [List interactions](/api/interactions/list-interactions) |
| `POST` | `/v1/sessions/{session_id}/interactions/{interaction_key}/answer` | [Answer an interaction](/api/interactions/answer-interaction) |
### Files
| Method | Path | Operation |
|---|---|---|
| `POST` | `/v1/sessions/{session_id}/files` | [Prepare a file](/api/files/prepare-file) |
| `GET` | `/v1/sessions/{session_id}/files` | [List files](/api/files/list-files) |
| `PUT` | `/v1/sessions/{session_id}/files/{file_id}/content` | [Upload file bytes](/api/files/upload-file-content) |
| `GET` | `/v1/sessions/{session_id}/files/{file_id}/content` | [Download file bytes](/api/files/download-file-content) |
| `DELETE` | `/v1/sessions/{session_id}/files/{file_id}` | [Delete a file](/api/files/delete-file) |
> **Availability:** Session-file endpoints appear only when file attachments are enabled for the environment. Read `file_attachments_enabled` from `GET /v1/whoami`, then `session_files_supported` on the session before showing those actions.
## Complete request flow
This managed-workspace example creates a reusable session, starts one run, and follows its events. It requires `curl` and `jq`. Copy the organization and workspace selectors from `GET /v1/me/workspaces`:
```bash title="Terminal"
export TANOMIS_API="https://api.tanomis.com"
export TANOMIS_TOKEN="paste-a-short-lived-access-token"
export TANOMIS_ORG="org_abc123"
export TANOMIS_WORKSPACE="customer-production"
SESSION_ID="$(
curl --silent --show-error --fail-with-body \\
--request POST \\
"$TANOMIS_API/v1/sessions" \\
--header "Authorization: Bearer $TANOMIS_TOKEN" \\
--header "X-Organization-ID: $TANOMIS_ORG" \\
--header "X-Workspace-Tenant-ID: $TANOMIS_WORKSPACE" \\
--header "Content-Type: application/json" \\
--data '{"agent_name":"support-copilot","idempotency_key":"api-example-session-1"}' \\
| jq --raw-output '.id'
)"
RUN_ID="$(
curl --silent --show-error --fail-with-body \\
--request POST \\
"$TANOMIS_API/v1/sessions/$SESSION_ID/runs" \\
--header "Authorization: Bearer $TANOMIS_TOKEN" \\
--header "X-Organization-ID: $TANOMIS_ORG" \\
--header "X-Workspace-Tenant-ID: $TANOMIS_WORKSPACE" \\
--header "Content-Type: application/json" \\
--data '{"input":{"prompt":"Summarize the open customer issue."},"idempotency_key":"api-example-run-1"}' \\
| jq --raw-output '.id'
)"
curl --no-buffer --fail-with-body \\
"$TANOMIS_API/v1/sessions/$SESSION_ID/runs/$RUN_ID/events" \\
--header "Authorization: Bearer $TANOMIS_TOKEN" \\
--header "X-Organization-ID: $TANOMIS_ORG" \\
--header "X-Workspace-Tenant-ID: $TANOMIS_WORKSPACE" \\
--header "Accept: text/event-stream"
```
When no more work will use the session, stop it with [Stop a session](/api/sessions/stop-session).
## Errors and retries
JSON errors include a machine-readable `error` code. Most application errors also include a human-readable `message`; clients must tolerate a code-only response from the access gate:
```json
{
"error": "invalid_status",
"message": "unknown interaction status"
}
```
| Status | Meaning | Client action |
|---|---|---|
| `400` | Invalid JSON, fields, selectors, or query parameters. | Fix the request; do not retry unchanged. |
| `401` | Missing, expired, or invalid access token. | Obtain a new token. |
| `403` | The caller does not have access to the selected organization/tenant projection or operation. | Change the grant or operation; do not retry unchanged. |
| `404` | The resource is absent in the selected tenant, or an optional capability is unavailable. | Check the selected workspace projection and resource id. |
| `409` | Lifecycle or idempotency conflict. | Read the returned or current resource before deciding whether to retry. |
| `412` | An idempotent create refers to a session that is already terminal. | Create a replacement with a new key if that is the intended action. |
| `429` | Request rate exceeded. | Retry with exponential backoff and jitter. |
| `500–599` | Temporary platform or upstream failure. | Retry boundedly with backoff. |
Only retry a create or run request automatically when it carries the same `idempotency_key` and the same request body. For event streams, reconnect with `Last-Event-ID` instead of starting a new run.
## Support boundary
The endpoints on this page are the supported application-invocation surface. Use the [CLI](/cli) for deployment and inspection and the [Agent manifest](/reference/agent-manifest) for version configuration. Routes used by the Tanomis control plane, an agent sandbox, or a platform operator are intentionally not part of this customer reference.
---
Source: /api/agents/invoke-agent
# Invoke once and stream
POST /v1/agents/{agent_name}/runs: Create a session and stream one run in a single request.
POST/v1/agents/{agent_name}/runs
Create a session and stream one run in a single request. The `200 OK` server-sent event stream carries `X-Session-ID` and `X-Run-ID` response headers; store both before processing events so the resources stay inspectable after a disconnect. This shortcut gives a retrying client no stable session boundary: use an explicit session for retry-safe admission, multiple runs, file attachments, durable human interaction, or independent reconnection.
Generated from the Platform API specification on 2026-09-04.
## Path parameters
| Parameter | Type | Description |
|---|---|---|
| `agent_name` | string | An agent enabled for the selected tenant, from List agents. |
## Headers
| Header | Required | Description |
|---|---|---|
| `Authorization` | yes | `Bearer` plus a short-lived OAuth access token issued for the Tanomis API audience. Backends obtain tokens with an [organization automation client](/guides/run/authenticate-your-backend); the CLI's human login is not an application credential. |
| `X-Organization-ID` | yes | The `organization_id` from the workspace projection you selected in `GET /v1/me/workspaces`. |
| `X-Workspace-Tenant-ID` | yes | The nested `tenant_id` from the same projection. A header cannot widen what the token grants. Some single-workspace or self-hosted environments take `X-Tenant-ID` instead, as supplied by their administrator. |
| `Accept` | yes | Send `text/event-stream`. |
## Request body
| Field | Type | Required | Description |
|---|---|---|---|
| `input` | object | required | Passed to the agent as JSON. The shape is defined by your agent's entrypoint. |
## Responses
| Status | Meaning |
|---|---|
| `200` | A server-sent event stream of the run's events; see Stream run events for the frame format and terminal events. |
## Request
```bash
curl -X POST "https://api.tanomis.com/v1/agents/support-copilot/runs" \
-H "Authorization: Bearer $TANOMIS_TOKEN" \
-H "X-Organization-ID: $TANOMIS_ORG" \
-H "X-Workspace-Tenant-ID: $TANOMIS_WORKSPACE" \
-H "Accept: text/event-stream" \
-H "Content-Type: application/json" \
-d '{
"input": {
"prompt": "Summarize the open customer issue."
}
}'
```
```python
import httpx
response = httpx.post(
f"https://api.tanomis.com/v1/agents/{agent_name}/runs",
headers={
"Authorization": f"Bearer {token}",
"X-Organization-ID": organization_id,
"X-Workspace-Tenant-ID": tenant_id,
"Accept": "text/event-stream",
},
json={
"input": {
"prompt": "Summarize the open customer issue.",
},
},
)
for line in response.iter_lines():
print(line)
```
```typescript
const response = await fetch(`https://api.tanomis.com/v1/agents/${agentName}/runs`, {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"X-Organization-ID": organizationId,
"X-Workspace-Tenant-ID": tenantId,
"Accept": "text/event-stream",
"Content-Type": "application/json",
},
body: JSON.stringify({
"input": {
"prompt": "Summarize the open customer issue."
}
}),
});
const reader = response.body.getReader();
// Decode server-sent events from the stream; see Stream and replay events.
```
---
Source: /api/agents/list-agents
# List agents
GET /v1/agents: Return the agents enabled for the selected tenant.
GET/v1/agents
Return the agents enabled for the selected tenant. Use `name` as `agent_name` when creating a session or in the one-shot run URL. The returned version id is informational and is not an invocation selector.
Generated from the Platform API specification on 2026-09-04.
## Headers
| Header | Required | Description |
|---|---|---|
| `Authorization` | yes | `Bearer` plus a short-lived OAuth access token issued for the Tanomis API audience. Backends obtain tokens with an [organization automation client](/guides/run/authenticate-your-backend); the CLI's human login is not an application credential. |
| `X-Organization-ID` | yes | The `organization_id` from the workspace projection you selected in `GET /v1/me/workspaces`. |
| `X-Workspace-Tenant-ID` | yes | The nested `tenant_id` from the same projection. A header cannot widen what the token grants. Some single-workspace or self-hosted environments take `X-Tenant-ID` instead, as supplied by their administrator. |
## Responses
| Status | Meaning |
|---|---|
| `200` | Agents enabled for the tenant. |
## Request
```bash
curl -X GET "https://api.tanomis.com/v1/agents" \
-H "Authorization: Bearer $TANOMIS_TOKEN" \
-H "X-Organization-ID: $TANOMIS_ORG" \
-H "X-Workspace-Tenant-ID: $TANOMIS_WORKSPACE"
```
```python
import httpx
response = httpx.get(
f"https://api.tanomis.com/v1/agents",
headers={
"Authorization": f"Bearer {token}",
"X-Organization-ID": organization_id,
"X-Workspace-Tenant-ID": tenant_id,
},
)
response.raise_for_status()
result = response.json()
```
```typescript
const response = await fetch(`https://api.tanomis.com/v1/agents`, {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`,
"X-Organization-ID": organizationId,
"X-Workspace-Tenant-ID": tenantId,
},
});
if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);
const result = await response.json();
```
## Response
```json title="200 OK"
{
"agents": [
{
"name": "support-copilot",
"scope": "global",
"version": "0.4.0",
"version_id": "ver_a82d1f",
"status": "production",
"framework": "claude-agents-sdk"
}
]
}
```
---
Source: /api/files/delete-file
# Delete a file
DELETE /v1/sessions/{session_id}/files/{file_id}: Delete a file from the session.
DELETE/v1/sessions/{session_id}/files/{file_id}
Delete a file from the session. Available even when the agent version lacks file support.
Generated from the Platform API specification on 2026-09-04.
## Path parameters
| Parameter | Type | Description |
|---|---|---|
| `session_id` | string | The session, as returned by Create a session. |
| `file_id` | string | The file, as returned by Prepare a file. |
## Headers
| Header | Required | Description |
|---|---|---|
| `Authorization` | yes | `Bearer` plus a short-lived OAuth access token issued for the Tanomis API audience. Backends obtain tokens with an [organization automation client](/guides/run/authenticate-your-backend); the CLI's human login is not an application credential. |
| `X-Organization-ID` | yes | The `organization_id` from the workspace projection you selected in `GET /v1/me/workspaces`. |
| `X-Workspace-Tenant-ID` | yes | The nested `tenant_id` from the same projection. A header cannot widen what the token grants. Some single-workspace or self-hosted environments take `X-Tenant-ID` instead, as supplied by their administrator. |
## Responses
| Status | Meaning |
|---|---|
| `204` | The file was deleted. |
| `404` | No such file, or attachments are disabled. |
## Request
```bash
curl -X DELETE "https://api.tanomis.com/v1/sessions/ses_7d4f9c/files/file_b172c9" \
-H "Authorization: Bearer $TANOMIS_TOKEN" \
-H "X-Organization-ID: $TANOMIS_ORG" \
-H "X-Workspace-Tenant-ID: $TANOMIS_WORKSPACE"
```
```python
import httpx
response = httpx.delete(
f"https://api.tanomis.com/v1/sessions/{session_id}/files/{file_id}",
headers={
"Authorization": f"Bearer {token}",
"X-Organization-ID": organization_id,
"X-Workspace-Tenant-ID": tenant_id,
},
)
for line in response.iter_lines():
print(line)
```
```typescript
const response = await fetch(`https://api.tanomis.com/v1/sessions/${sessionId}/files/${fileId}`, {
method: "DELETE",
headers: {
"Authorization": `Bearer ${token}`,
"X-Organization-ID": organizationId,
"X-Workspace-Tenant-ID": tenantId,
},
});
const reader = response.body.getReader();
// Decode server-sent events from the stream; see Stream and replay events.
```
---
Source: /api/files/download-file-content
# Download file bytes
GET /v1/sessions/{session_id}/files/{file_id}/content: Download the file's bytes.
GET/v1/sessions/{session_id}/files/{file_id}/content
Download the file's bytes.
Generated from the Platform API specification on 2026-09-04.
## Path parameters
| Parameter | Type | Description |
|---|---|---|
| `session_id` | string | The session, as returned by Create a session. |
| `file_id` | string | The file, as returned by Prepare a file. |
## Headers
| Header | Required | Description |
|---|---|---|
| `Authorization` | yes | `Bearer` plus a short-lived OAuth access token issued for the Tanomis API audience. Backends obtain tokens with an [organization automation client](/guides/run/authenticate-your-backend); the CLI's human login is not an application credential. |
| `X-Organization-ID` | yes | The `organization_id` from the workspace projection you selected in `GET /v1/me/workspaces`. |
| `X-Workspace-Tenant-ID` | yes | The nested `tenant_id` from the same projection. A header cannot widen what the token grants. Some single-workspace or self-hosted environments take `X-Tenant-ID` instead, as supplied by their administrator. |
## Responses
| Status | Meaning |
|---|---|
| `200` | The bytes, with the file's media type. |
| `404` | No such file, or attachments are disabled. |
## Request
```bash
curl -X GET "https://api.tanomis.com/v1/sessions/ses_7d4f9c/files/file_b172c9/content" \
-H "Authorization: Bearer $TANOMIS_TOKEN" \
-H "X-Organization-ID: $TANOMIS_ORG" \
-H "X-Workspace-Tenant-ID: $TANOMIS_WORKSPACE"
```
```python
import httpx
response = httpx.get(
f"https://api.tanomis.com/v1/sessions/{session_id}/files/{file_id}/content",
headers={
"Authorization": f"Bearer {token}",
"X-Organization-ID": organization_id,
"X-Workspace-Tenant-ID": tenant_id,
},
)
for line in response.iter_lines():
print(line)
```
```typescript
const response = await fetch(`https://api.tanomis.com/v1/sessions/${sessionId}/files/${fileId}/content`, {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`,
"X-Organization-ID": organizationId,
"X-Workspace-Tenant-ID": tenantId,
},
});
const reader = response.body.getReader();
// Decode server-sent events from the stream; see Stream and replay events.
```
---
Source: /api/files/list-files
# List files
GET /v1/sessions/{session_id}/files: List the files in the session with their status.
GET/v1/sessions/{session_id}/files
List the files in the session with their status. Available even when the agent version lacks file support, so existing files can be inspected.
Generated from the Platform API specification on 2026-09-04.
## Path parameters
| Parameter | Type | Description |
|---|---|---|
| `session_id` | string | The session, as returned by Create a session. |
## Headers
| Header | Required | Description |
|---|---|---|
| `Authorization` | yes | `Bearer` plus a short-lived OAuth access token issued for the Tanomis API audience. Backends obtain tokens with an [organization automation client](/guides/run/authenticate-your-backend); the CLI's human login is not an application credential. |
| `X-Organization-ID` | yes | The `organization_id` from the workspace projection you selected in `GET /v1/me/workspaces`. |
| `X-Workspace-Tenant-ID` | yes | The nested `tenant_id` from the same projection. A header cannot widen what the token grants. Some single-workspace or self-hosted environments take `X-Tenant-ID` instead, as supplied by their administrator. |
## Responses
| Status | Meaning |
|---|---|
| `200` | The session's files. |
## Request
```bash
curl -X GET "https://api.tanomis.com/v1/sessions/ses_7d4f9c/files" \
-H "Authorization: Bearer $TANOMIS_TOKEN" \
-H "X-Organization-ID: $TANOMIS_ORG" \
-H "X-Workspace-Tenant-ID: $TANOMIS_WORKSPACE"
```
```python
import httpx
response = httpx.get(
f"https://api.tanomis.com/v1/sessions/{session_id}/files",
headers={
"Authorization": f"Bearer {token}",
"X-Organization-ID": organization_id,
"X-Workspace-Tenant-ID": tenant_id,
},
)
response.raise_for_status()
result = response.json()
```
```typescript
const response = await fetch(`https://api.tanomis.com/v1/sessions/${sessionId}/files`, {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`,
"X-Organization-ID": organizationId,
"X-Workspace-Tenant-ID": tenantId,
},
});
if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);
const result = await response.json();
```
## Response
```json title="200 OK"
{
"files": [
{
"id": "file_b172c9",
"filename": "case-summary.pdf",
"media_type": "application/pdf",
"size_bytes": 48213,
"status": "ready",
"created_at": "2026-09-02T11:45:00Z"
}
]
}
```
---
Source: /api/files/prepare-file
# Prepare a file
POST /v1/sessions/{session_id}/files: Prepare file metadata before uploading bytes.
POST/v1/sessions/{session_id}/files
Prepare file metadata before uploading bytes. Check `session_files_supported` on the session first. The response carries an opaque `id` and a `content_url`; upload exactly `size_bytes` bytes there. File routes return `404` when attachments are disabled for the environment, and prepare and content transfer return `409` when this agent version lacks file support.
Generated from the Platform API specification on 2026-09-04.
## Path parameters
| Parameter | Type | Description |
|---|---|---|
| `session_id` | string | The session, as returned by Create a session. |
## Headers
| Header | Required | Description |
|---|---|---|
| `Authorization` | yes | `Bearer` plus a short-lived OAuth access token issued for the Tanomis API audience. Backends obtain tokens with an [organization automation client](/guides/run/authenticate-your-backend); the CLI's human login is not an application credential. |
| `X-Organization-ID` | yes | The `organization_id` from the workspace projection you selected in `GET /v1/me/workspaces`. |
| `X-Workspace-Tenant-ID` | yes | The nested `tenant_id` from the same projection. A header cannot widen what the token grants. Some single-workspace or self-hosted environments take `X-Tenant-ID` instead, as supplied by their administrator. |
## Request body
| Field | Type | Required | Description |
|---|---|---|---|
| `filename` | string | required | |
| `media_type` | string | required | |
| `size_bytes` | integer | required | |
| `sha256` | string | required | Hex digest of the bytes you will upload. |
| `idempotency_key` | string | optional | Replays the same prepared file for the same request. |
## Responses
| Status | Meaning |
|---|---|
| `201` | The prepared file. |
| `404` | File attachments are disabled for the environment, or no such session. |
| `409` | The deployed agent version does not support files. |
## Request
```bash
curl -X POST "https://api.tanomis.com/v1/sessions/ses_7d4f9c/files" \
-H "Authorization: Bearer $TANOMIS_TOKEN" \
-H "X-Organization-ID: $TANOMIS_ORG" \
-H "X-Workspace-Tenant-ID: $TANOMIS_WORKSPACE" \
-H "Content-Type: application/json" \
-d '{
"filename": "case-summary.pdf",
"media_type": "application/pdf",
"size_bytes": 48213,
"sha256": "83e0ef2f426dfad68f9b420304fc19414f8451a6bf24667f7e61c50e9ca57f14",
"idempotency_key": "case-summary-v1"
}'
```
```python
import httpx
response = httpx.post(
f"https://api.tanomis.com/v1/sessions/{session_id}/files",
headers={
"Authorization": f"Bearer {token}",
"X-Organization-ID": organization_id,
"X-Workspace-Tenant-ID": tenant_id,
},
json={
"filename": "case-summary.pdf",
"media_type": "application/pdf",
"size_bytes": 48213,
"sha256": "83e0ef2f426dfad68f9b420304fc19414f8451a6bf24667f7e61c50e9ca57f14",
"idempotency_key": "case-summary-v1",
},
)
response.raise_for_status()
result = response.json()
```
```typescript
const response = await fetch(`https://api.tanomis.com/v1/sessions/${sessionId}/files`, {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"X-Organization-ID": organizationId,
"X-Workspace-Tenant-ID": tenantId,
"Content-Type": "application/json",
},
body: JSON.stringify({
"filename": "case-summary.pdf",
"media_type": "application/pdf",
"size_bytes": 48213,
"sha256": "83e0ef2f426dfad68f9b420304fc19414f8451a6bf24667f7e61c50e9ca57f14",
"idempotency_key": "case-summary-v1"
}),
});
if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);
const result = await response.json();
```
## Response
```json title="201 Created"
{
"id": "file_b172c9",
"filename": "case-summary.pdf",
"media_type": "application/pdf",
"size_bytes": 48213,
"sha256": "83e0ef2f426dfad68f9b420304fc19414f8451a6bf24667f7e61c50e9ca57f14",
"status": "prepared",
"content_url": "https://api.tanomis.com/v1/sessions/ses_7d4f9c/files/file_b172c9/content",
"created_at": "2026-09-02T11:45:00Z"
}
```
---
Source: /api/files/upload-file-content
# Upload file bytes
PUT /v1/sessions/{session_id}/files/{file_id}/content: Upload the prepared file's bytes with `Content-Type: application/octet-stream` and a `Content-Length` equal to the prepared `size_bytes`.
PUT/v1/sessions/{session_id}/files/{file_id}/content
Upload the prepared file's bytes with `Content-Type: application/octet-stream` and a `Content-Length` equal to the prepared `size_bytes`. The file becomes `ready` when the digest matches.
Generated from the Platform API specification on 2026-09-04.
## Path parameters
| Parameter | Type | Description |
|---|---|---|
| `session_id` | string | The session, as returned by Create a session. |
| `file_id` | string | The file, as returned by Prepare a file. |
## Headers
| Header | Required | Description |
|---|---|---|
| `Authorization` | yes | `Bearer` plus a short-lived OAuth access token issued for the Tanomis API audience. Backends obtain tokens with an [organization automation client](/guides/run/authenticate-your-backend); the CLI's human login is not an application credential. |
| `X-Organization-ID` | yes | The `organization_id` from the workspace projection you selected in `GET /v1/me/workspaces`. |
| `X-Workspace-Tenant-ID` | yes | The nested `tenant_id` from the same projection. A header cannot widen what the token grants. Some single-workspace or self-hosted environments take `X-Tenant-ID` instead, as supplied by their administrator. |
| `Content-Type` | yes | Send `application/octet-stream`. |
## Request body
The raw bytes of the file, sent as `application/octet-stream`.
## Responses
| Status | Meaning |
|---|---|
| `200` | The file, now `ready`. |
| `404` | No such prepared file, or attachments are disabled. |
| `409` | The agent version lacks file support, or the bytes did not match the prepared size and digest. |
## Request
```bash
curl -X PUT "https://api.tanomis.com/v1/sessions/ses_7d4f9c/files/file_b172c9/content" \
-H "Authorization: Bearer $TANOMIS_TOKEN" \
-H "X-Organization-ID: $TANOMIS_ORG" \
-H "X-Workspace-Tenant-ID: $TANOMIS_WORKSPACE" \
-H "Content-Type: application/octet-stream" \
--data-binary @case-summary.pdf
```
```python
import httpx
response = httpx.put(
f"https://api.tanomis.com/v1/sessions/{session_id}/files/{file_id}/content",
headers={
"Authorization": f"Bearer {token}",
"X-Organization-ID": organization_id,
"X-Workspace-Tenant-ID": tenant_id,
"Content-Type": "application/octet-stream",
},
content=open("case-summary.pdf", "rb").read(),
)
response.raise_for_status()
result = response.json()
```
```typescript
const response = await fetch(`https://api.tanomis.com/v1/sessions/${sessionId}/files/${fileId}/content`, {
method: "PUT",
headers: {
"Authorization": `Bearer ${token}`,
"X-Organization-ID": organizationId,
"X-Workspace-Tenant-ID": tenantId,
"Content-Type": "application/octet-stream",
},
body: await readFile("case-summary.pdf"),
});
if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);
const result = await response.json();
```
## Response
```json title="200 OK"
{
"id": "file_b172c9",
"filename": "case-summary.pdf",
"media_type": "application/pdf",
"size_bytes": 48213,
"status": "ready",
"created_at": "2026-09-02T11:45:00Z"
}
```
---
Source: /api/interactions/answer-interaction
# Answer an interaction
POST /v1/sessions/{session_id}/interactions/{interaction_key}/answer: Accept a durable human answer by the stable `interaction_key`.
POST/v1/sessions/{session_id}/interactions/{interaction_key}/answer
Accept a durable human answer by the stable `interaction_key`. `answer` is JSON: a string for a question, or the decision object requested by a permission interaction. A successful answer returns `202 Accepted` with status `answered`; delivery is asynchronous, so list the session's interactions again until the key's status is `delivered`. If another caller already answered, the API returns `409 Conflict`; list again to read the recorded answer.
Generated from the Platform API specification on 2026-09-04.
## Path parameters
| Parameter | Type | Description |
|---|---|---|
| `session_id` | string | The session, as returned by Create a session. |
| `interaction_key` | string | The stable key from the interactions list. |
## Headers
| Header | Required | Description |
|---|---|---|
| `Authorization` | yes | `Bearer` plus a short-lived OAuth access token issued for the Tanomis API audience. Backends obtain tokens with an [organization automation client](/guides/run/authenticate-your-backend); the CLI's human login is not an application credential. |
| `X-Organization-ID` | yes | The `organization_id` from the workspace projection you selected in `GET /v1/me/workspaces`. |
| `X-Workspace-Tenant-ID` | yes | The nested `tenant_id` from the same projection. A header cannot widen what the token grants. Some single-workspace or self-hosted environments take `X-Tenant-ID` instead, as supplied by their administrator. |
## Request body
| Field | Type | Required | Description |
|---|---|---|---|
| `kind` | string | required | Must match the interaction's kind: `question` or `permission`. |
| `answer` | any | required | A string for a question; the requested decision object for a permission. |
## Responses
| Status | Meaning |
|---|---|
| `202` | The answer was accepted. |
| `404` | No such interaction in this session. |
| `409` | Another caller already answered this interaction. |
## Request
```bash
curl -X POST "https://api.tanomis.com/v1/sessions/ses_7d4f9c/interactions/ihk_91f0/answer" \
-H "Authorization: Bearer $TANOMIS_TOKEN" \
-H "X-Organization-ID: $TANOMIS_ORG" \
-H "X-Workspace-Tenant-ID: $TANOMIS_WORKSPACE" \
-H "Content-Type: application/json" \
-d '{
"kind": "question",
"answer": "Include the approved credit."
}'
```
```python
import httpx
response = httpx.post(
f"https://api.tanomis.com/v1/sessions/{session_id}/interactions/{interaction_key}/answer",
headers={
"Authorization": f"Bearer {token}",
"X-Organization-ID": organization_id,
"X-Workspace-Tenant-ID": tenant_id,
},
json={
"kind": "question",
"answer": "Include the approved credit.",
},
)
response.raise_for_status()
result = response.json()
```
```typescript
const response = await fetch(`https://api.tanomis.com/v1/sessions/${sessionId}/interactions/${interactionKey}/answer`, {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"X-Organization-ID": organizationId,
"X-Workspace-Tenant-ID": tenantId,
"Content-Type": "application/json",
},
body: JSON.stringify({
"kind": "question",
"answer": "Include the approved credit."
}),
});
if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);
const result = await response.json();
```
## Response
```json title="202 Accepted"
{
"interaction_key": "ihk_91f0",
"run_id": "run_a12bc3",
"kind": "question",
"status": "answered"
}
```
---
Source: /api/interactions/list-interactions
# List interactions
GET /v1/sessions/{session_id}/interactions: List the session's human questions and permission decisions.
GET/v1/sessions/{session_id}/interactions
List the session's human questions and permission decisions. Filter with `status=pending` to find what needs an answer. Durable interactions are enabled in hosted Tanomis; another environment may not enable them, and there is no capability field to probe today, so confirm support with that environment's administrator.
Generated from the Platform API specification on 2026-09-04.
## Path parameters
| Parameter | Type | Description |
|---|---|---|
| `session_id` | string | The session, as returned by Create a session. |
## Query parameters
| Parameter | Type | Description |
|---|---|---|
| `status` | string | Only interactions in this status, for example `pending`. |
## Headers
| Header | Required | Description |
|---|---|---|
| `Authorization` | yes | `Bearer` plus a short-lived OAuth access token issued for the Tanomis API audience. Backends obtain tokens with an [organization automation client](/guides/run/authenticate-your-backend); the CLI's human login is not an application credential. |
| `X-Organization-ID` | yes | The `organization_id` from the workspace projection you selected in `GET /v1/me/workspaces`. |
| `X-Workspace-Tenant-ID` | yes | The nested `tenant_id` from the same projection. A header cannot widen what the token grants. Some single-workspace or self-hosted environments take `X-Tenant-ID` instead, as supplied by their administrator. |
## Responses
| Status | Meaning |
|---|---|
| `200` | The session's interactions. |
## Request
```bash
curl -X GET "https://api.tanomis.com/v1/sessions/ses_7d4f9c/interactions" \
-H "Authorization: Bearer $TANOMIS_TOKEN" \
-H "X-Organization-ID: $TANOMIS_ORG" \
-H "X-Workspace-Tenant-ID: $TANOMIS_WORKSPACE"
```
```python
import httpx
response = httpx.get(
f"https://api.tanomis.com/v1/sessions/{session_id}/interactions",
headers={
"Authorization": f"Bearer {token}",
"X-Organization-ID": organization_id,
"X-Workspace-Tenant-ID": tenant_id,
},
)
response.raise_for_status()
result = response.json()
```
```typescript
const response = await fetch(`https://api.tanomis.com/v1/sessions/${sessionId}/interactions`, {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`,
"X-Organization-ID": organizationId,
"X-Workspace-Tenant-ID": tenantId,
},
});
if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);
const result = await response.json();
```
## Response
```json title="200 OK"
{
"interactions": [
{
"interaction_key": "ihk_91f0",
"run_id": "run_a12bc3",
"kind": "question",
"status": "pending",
"payload": {
"question": "Should I include the account credit?"
},
"created_at": "2026-09-02T11:43:08Z",
"expires_at": "2026-09-02T12:43:08Z"
}
]
}
```
---
Source: /api/runs/cancel-run
# Cancel a run
POST /v1/sessions/{session_id}/runs/{run_id}/cancel: Request cancellation of an active run.
POST/v1/sessions/{session_id}/runs/{run_id}/cancel
Request cancellation of an active run. Cancellation is asynchronous: follow the event stream for `run_cancelled` or read the run until its status is terminal.
Generated from the Platform API specification on 2026-09-04.
## Path parameters
| Parameter | Type | Description |
|---|---|---|
| `session_id` | string | The session, as returned by Create a session. |
| `run_id` | string | The run, as returned by Start a run. |
## Headers
| Header | Required | Description |
|---|---|---|
| `Authorization` | yes | `Bearer` plus a short-lived OAuth access token issued for the Tanomis API audience. Backends obtain tokens with an [organization automation client](/guides/run/authenticate-your-backend); the CLI's human login is not an application credential. |
| `X-Organization-ID` | yes | The `organization_id` from the workspace projection you selected in `GET /v1/me/workspaces`. |
| `X-Workspace-Tenant-ID` | yes | The nested `tenant_id` from the same projection. A header cannot widen what the token grants. Some single-workspace or self-hosted environments take `X-Tenant-ID` instead, as supplied by their administrator. |
## Responses
| Status | Meaning |
|---|---|
| `202` | Cancellation was requested. |
| `404` | No such run in this session. |
| `409` | The run is already terminal. |
## Request
```bash
curl -X POST "https://api.tanomis.com/v1/sessions/ses_7d4f9c/runs/run_a12bc3/cancel" \
-H "Authorization: Bearer $TANOMIS_TOKEN" \
-H "X-Organization-ID: $TANOMIS_ORG" \
-H "X-Workspace-Tenant-ID: $TANOMIS_WORKSPACE"
```
```python
import httpx
response = httpx.post(
f"https://api.tanomis.com/v1/sessions/{session_id}/runs/{run_id}/cancel",
headers={
"Authorization": f"Bearer {token}",
"X-Organization-ID": organization_id,
"X-Workspace-Tenant-ID": tenant_id,
},
)
response.raise_for_status()
result = response.json()
```
```typescript
const response = await fetch(`https://api.tanomis.com/v1/sessions/${sessionId}/runs/${runId}/cancel`, {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"X-Organization-ID": organizationId,
"X-Workspace-Tenant-ID": tenantId,
},
});
if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);
const result = await response.json();
```
## Response
```json title="202 Accepted"
{
"id": "run_a12bc3",
"session_id": "ses_7d4f9c",
"status": "running",
"started_at": "2026-09-02T11:43:02Z"
}
```
---
Source: /api/runs/get-run
# Read a run
GET /v1/sessions/{session_id}/runs/{run_id}: Read one run.
GET/v1/sessions/{session_id}/runs/{run_id}
Read one run. The run resource is the authoritative status; after an event stream ends without a terminal frame, read it here before deciding whether to reconnect.
Generated from the Platform API specification on 2026-09-04.
## Path parameters
| Parameter | Type | Description |
|---|---|---|
| `session_id` | string | The session, as returned by Create a session. |
| `run_id` | string | The run, as returned by Start a run. |
## Headers
| Header | Required | Description |
|---|---|---|
| `Authorization` | yes | `Bearer` plus a short-lived OAuth access token issued for the Tanomis API audience. Backends obtain tokens with an [organization automation client](/guides/run/authenticate-your-backend); the CLI's human login is not an application credential. |
| `X-Organization-ID` | yes | The `organization_id` from the workspace projection you selected in `GET /v1/me/workspaces`. |
| `X-Workspace-Tenant-ID` | yes | The nested `tenant_id` from the same projection. A header cannot widen what the token grants. Some single-workspace or self-hosted environments take `X-Tenant-ID` instead, as supplied by their administrator. |
## Responses
| Status | Meaning |
|---|---|
| `200` | The run. |
| `404` | No such run in this session. |
## Request
```bash
curl -X GET "https://api.tanomis.com/v1/sessions/ses_7d4f9c/runs/run_a12bc3" \
-H "Authorization: Bearer $TANOMIS_TOKEN" \
-H "X-Organization-ID: $TANOMIS_ORG" \
-H "X-Workspace-Tenant-ID: $TANOMIS_WORKSPACE"
```
```python
import httpx
response = httpx.get(
f"https://api.tanomis.com/v1/sessions/{session_id}/runs/{run_id}",
headers={
"Authorization": f"Bearer {token}",
"X-Organization-ID": organization_id,
"X-Workspace-Tenant-ID": tenant_id,
},
)
response.raise_for_status()
result = response.json()
```
```typescript
const response = await fetch(`https://api.tanomis.com/v1/sessions/${sessionId}/runs/${runId}`, {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`,
"X-Organization-ID": organizationId,
"X-Workspace-Tenant-ID": tenantId,
},
});
if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);
const result = await response.json();
```
## Response
```json title="200 OK"
{
"id": "run_a12bc3",
"session_id": "ses_7d4f9c",
"status": "running",
"started_at": "2026-09-02T11:43:02Z"
}
```
---
Source: /api/runs/list-runs
# List runs
GET /v1/sessions/{session_id}/runs: List the session's runs.
GET/v1/sessions/{session_id}/runs
List the session's runs. Terminal run states are `succeeded`, `failed`, and `cancelled`; failed runs may carry `error_code` and `error_message`.
Generated from the Platform API specification on 2026-09-04.
## Path parameters
| Parameter | Type | Description |
|---|---|---|
| `session_id` | string | The session, as returned by Create a session. |
## Headers
| Header | Required | Description |
|---|---|---|
| `Authorization` | yes | `Bearer` plus a short-lived OAuth access token issued for the Tanomis API audience. Backends obtain tokens with an [organization automation client](/guides/run/authenticate-your-backend); the CLI's human login is not an application credential. |
| `X-Organization-ID` | yes | The `organization_id` from the workspace projection you selected in `GET /v1/me/workspaces`. |
| `X-Workspace-Tenant-ID` | yes | The nested `tenant_id` from the same projection. A header cannot widen what the token grants. Some single-workspace or self-hosted environments take `X-Tenant-ID` instead, as supplied by their administrator. |
## Responses
| Status | Meaning |
|---|---|
| `200` | The session's runs. |
## Request
```bash
curl -X GET "https://api.tanomis.com/v1/sessions/ses_7d4f9c/runs" \
-H "Authorization: Bearer $TANOMIS_TOKEN" \
-H "X-Organization-ID: $TANOMIS_ORG" \
-H "X-Workspace-Tenant-ID: $TANOMIS_WORKSPACE"
```
```python
import httpx
response = httpx.get(
f"https://api.tanomis.com/v1/sessions/{session_id}/runs",
headers={
"Authorization": f"Bearer {token}",
"X-Organization-ID": organization_id,
"X-Workspace-Tenant-ID": tenant_id,
},
)
response.raise_for_status()
result = response.json()
```
```typescript
const response = await fetch(`https://api.tanomis.com/v1/sessions/${sessionId}/runs`, {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`,
"X-Organization-ID": organizationId,
"X-Workspace-Tenant-ID": tenantId,
},
});
if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);
const result = await response.json();
```
## Response
```json title="200 OK"
{
"runs": [
{
"id": "run_a12bc3",
"session_id": "ses_7d4f9c",
"status": "succeeded",
"started_at": "2026-09-02T11:43:02Z"
}
]
}
```
---
Source: /api/runs/start-run
# Start a run
POST /v1/sessions/{session_id}/runs: Start one run inside an existing session.
POST/v1/sessions/{session_id}/runs
Start one run inside an existing session. The session serializes its runs. `idempotency_key` is scoped to the session and prevents a retry from starting a second run; a request without a key always creates a new run. By default the endpoint returns `202 Accepted` with the admitted run; set `Accept: text/event-stream` or add `?stream=true` to stream immediately, or reconnect later through the run's events endpoint.
Generated from the Platform API specification on 2026-09-04.
## Path parameters
| Parameter | Type | Description |
|---|---|---|
| `session_id` | string | The session, as returned by Create a session. |
## Query parameters
| Parameter | Type | Description |
|---|---|---|
| `stream` | boolean | Stream the run's events instead of returning the admitted run. |
## Headers
| Header | Required | Description |
|---|---|---|
| `Authorization` | yes | `Bearer` plus a short-lived OAuth access token issued for the Tanomis API audience. Backends obtain tokens with an [organization automation client](/guides/run/authenticate-your-backend); the CLI's human login is not an application credential. |
| `X-Organization-ID` | yes | The `organization_id` from the workspace projection you selected in `GET /v1/me/workspaces`. |
| `X-Workspace-Tenant-ID` | yes | The nested `tenant_id` from the same projection. A header cannot widen what the token grants. Some single-workspace or self-hosted environments take `X-Tenant-ID` instead, as supplied by their administrator. |
## Request body
| Field | Type | Required | Description |
|---|---|---|---|
| `input` | object | required | Passed to the agent as JSON. The shape is defined by your agent's entrypoint. |
| `idempotency_key` | string | optional | Scoped to the session; a retry with the same key returns the original run. At most 255 bytes. |
| `attachments` | array | optional | Files prepared and uploaded in this session, as `{ "file_id": ... }` objects. |
| `work` | object | optional | Settlement-mode agents only. Send either `subject` (open new work) or `work_id` (continue work opened by an earlier session). |
| `work.subject` | string | optional | Opens new work, for example `invoice:INV-1842`. |
| `work.work_id` | string | optional | Continues work opened by an earlier session. |
| `work.contract_ref` | string | optional | Sent together with `contract_version` when opening work. Cannot be added or changed later. |
| `work.contract_version` | string | optional | Sent together with `contract_ref`. |
## Responses
| Status | Meaning |
|---|---|
| `200` | With streaming requested: the run's server-sent event stream. |
| `202` | The run was admitted. Poll it or open the event stream. |
| `404` | No such session in the selected tenant. |
| `409` | The idempotency key matched a run with a different body, or the session cannot admit a run. |
## Request
```bash
curl -X POST "https://api.tanomis.com/v1/sessions/ses_7d4f9c/runs" \
-H "Authorization: Bearer $TANOMIS_TOKEN" \
-H "X-Organization-ID: $TANOMIS_ORG" \
-H "X-Workspace-Tenant-ID: $TANOMIS_WORKSPACE" \
-H "Content-Type: application/json" \
-d '{
"input": {
"prompt": "Draft a concise reply for the customer."
},
"idempotency_key": "support-case-1842-reply-1"
}'
```
```python
import httpx
response = httpx.post(
f"https://api.tanomis.com/v1/sessions/{session_id}/runs",
headers={
"Authorization": f"Bearer {token}",
"X-Organization-ID": organization_id,
"X-Workspace-Tenant-ID": tenant_id,
},
json={
"input": {
"prompt": "Draft a concise reply for the customer.",
},
"idempotency_key": "support-case-1842-reply-1",
},
)
response.raise_for_status()
result = response.json()
```
```typescript
const response = await fetch(`https://api.tanomis.com/v1/sessions/${sessionId}/runs`, {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"X-Organization-ID": organizationId,
"X-Workspace-Tenant-ID": tenantId,
"Content-Type": "application/json",
},
body: JSON.stringify({
"input": {
"prompt": "Draft a concise reply for the customer."
},
"idempotency_key": "support-case-1842-reply-1"
}),
});
if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);
const result = await response.json();
```
## Response
```json title="202 Accepted"
{
"id": "run_a12bc3",
"session_id": "ses_7d4f9c",
"status": "running",
"started_at": "2026-09-02T11:43:02Z"
}
```
## Attach files
Reference files that were prepared and uploaded in this session (see Prepare a file) when starting the run.
```json title="Request"
{
"input": {
"prompt": "Summarize the attached case."
},
"attachments": [
{
"file_id": "file_b172c9"
}
],
"idempotency_key": "case-summary-run-v1"
}
```
## Settlement-mode agents
For a settlement-mode agent, the first run in a new session must declare the work it serves. Use `work.subject` to open new work, or `work.work_id` to continue work opened by an earlier session; send either, never both. When opening work, `contract_ref` and `contract_version` may establish a contract and must be sent together. Later requests may omit the
contract or restate that exact pair, but cannot add or change it. The admitted run includes `settlement_work_id`; store it if another session may need to continue the same work. Standard-mode agents do not require a `work` object. Durable work and settlement are tenant capabilities: for a version in `settlement` mode, an unavailable settlement boundary fails session activation rather than silently running without it.
```json title="Request"
{
"input": {
"prompt": "Reconcile invoice INV-1842."
},
"work": {
"subject": "invoice:INV-1842",
"contract_ref": "billing/reconciliation",
"contract_version": "2026-09"
},
"idempotency_key": "invoice-1842-attempt-1"
}
```
```json title="Response"
{
"id": "run_a12bc3",
"session_id": "ses_7d4f9c",
"status": "running",
"started_at": "2026-09-02T11:43:02Z",
"settlement_work_id": "work_f61ca9"
}
```
---
Source: /api/runs/stream-run-events
# Stream run events
GET /v1/sessions/{session_id}/runs/{run_id}/events: Stream or replay the run's events as server-sent events.
GET/v1/sessions/{session_id}/runs/{run_id}/events
Stream or replay the run's events as server-sent events. Each frame carries an SSE `id`, an `event` name, and the complete event object as JSON in `data`. Terminal event types are `run_completed`, `run_cancelled`, and `error`. To reconnect after a disconnect, send `Last-Event-ID` with the last id you processed. Replay starts strictly after `Last-Event-ID` when live or persisted history is available. Event ids order events only within one run: treat them as opaque strings, store them with the session and run ids, and deduplicate by run id plus event id. A stream can close cleanly without a terminal frame when no events are available; read the run resource for its authoritative status and reconnect only while it remains non-terminal. Ignore unknown event names and fields.
Generated from the Platform API specification on 2026-09-04.
## Path parameters
| Parameter | Type | Description |
|---|---|---|
| `session_id` | string | The session, as returned by Create a session. |
| `run_id` | string | The run, as returned by Start a run. |
## Headers
| Header | Required | Description |
|---|---|---|
| `Authorization` | yes | `Bearer` plus a short-lived OAuth access token issued for the Tanomis API audience. Backends obtain tokens with an [organization automation client](/guides/run/authenticate-your-backend); the CLI's human login is not an application credential. |
| `X-Organization-ID` | yes | The `organization_id` from the workspace projection you selected in `GET /v1/me/workspaces`. |
| `X-Workspace-Tenant-ID` | yes | The nested `tenant_id` from the same projection. A header cannot widen what the token grants. Some single-workspace or self-hosted environments take `X-Tenant-ID` instead, as supplied by their administrator. |
| `Accept` | yes | Send `text/event-stream`. |
| `Last-Event-ID` | no | Send `14`. |
## Responses
| Status | Meaning |
|---|---|
| `200` | Server-sent events. |
| `404` | No such run in this session. |
## Request
```bash
curl -X GET "https://api.tanomis.com/v1/sessions/ses_7d4f9c/runs/run_a12bc3/events" \
-H "Authorization: Bearer $TANOMIS_TOKEN" \
-H "X-Organization-ID: $TANOMIS_ORG" \
-H "X-Workspace-Tenant-ID: $TANOMIS_WORKSPACE" \
-H "Accept: text/event-stream" \
-H "Last-Event-ID: 14"
```
```python
import httpx
response = httpx.get(
f"https://api.tanomis.com/v1/sessions/{session_id}/runs/{run_id}/events",
headers={
"Authorization": f"Bearer {token}",
"X-Organization-ID": organization_id,
"X-Workspace-Tenant-ID": tenant_id,
"Accept": "text/event-stream",
"Last-Event-ID": "14",
},
)
for line in response.iter_lines():
print(line)
```
```typescript
const response = await fetch(`https://api.tanomis.com/v1/sessions/${sessionId}/runs/${runId}/events`, {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`,
"X-Organization-ID": organizationId,
"X-Workspace-Tenant-ID": tenantId,
"Accept": "text/event-stream",
"Last-Event-ID": "14",
},
});
const reader = response.body.getReader();
// Decode server-sent events from the stream; see Stream and replay events.
```
## Response
```text title="200 OK · text/event-stream"
id: 14
event: message_delta
data: {"id":"14","type":"message_delta","session_id":"ses_7d4f9c","run_id":"run_a12bc3","created_at":"2026-09-03T09:00:00.014Z","data":{"text":"I found"}}
```
---
Source: /api/sessions/create-session
# Create a session
POST /v1/sessions: Create a reusable session for one enabled agent.
POST/v1/sessions
Create a reusable session for one enabled agent. A new session returns `201 Created`. While that session is non-terminal, replaying the same `idempotency_key` with the same provisioning inputs returns it with `200 OK`; the same key with different inputs returns `409 Conflict`. Once the original session is stopped, failed, or expired, replay returns `412 Precondition Failed` instead of creating a replacement.
Generated from the Platform API specification on 2026-09-04.
## Headers
| Header | Required | Description |
|---|---|---|
| `Authorization` | yes | `Bearer` plus a short-lived OAuth access token issued for the Tanomis API audience. Backends obtain tokens with an [organization automation client](/guides/run/authenticate-your-backend); the CLI's human login is not an application credential. |
| `X-Organization-ID` | yes | The `organization_id` from the workspace projection you selected in `GET /v1/me/workspaces`. |
| `X-Workspace-Tenant-ID` | yes | The nested `tenant_id` from the same projection. A header cannot widen what the token grants. Some single-workspace or self-hosted environments take `X-Tenant-ID` instead, as supplied by their administrator. |
## Request body
| Field | Type | Required | Description |
|---|---|---|---|
| `agent_name` | string | required | An agent enabled for the selected tenant. |
| `idempotency_key` | string | optional | Replays the same session for the same caller and request. At most 255 bytes. |
| `model` | string | optional | A model allowed by the deployed agent version. |
| `agent_inputs` | object | optional | Agent-specific initialization data. |
## Responses
| Status | Meaning |
|---|---|
| `200` | An idempotent replay returned the existing non-terminal session. |
| `201` | The session was created. |
| `409` | The idempotency key matched a session created with different provisioning inputs. |
| `412` | The idempotency key refers to a session that is already terminal. |
## Request
```bash
curl -X POST "https://api.tanomis.com/v1/sessions" \
-H "Authorization: Bearer $TANOMIS_TOKEN" \
-H "X-Organization-ID: $TANOMIS_ORG" \
-H "X-Workspace-Tenant-ID: $TANOMIS_WORKSPACE" \
-H "Content-Type: application/json" \
-d '{
"agent_name": "support-copilot",
"idempotency_key": "support-case-1842"
}'
```
```python
import httpx
response = httpx.post(
f"https://api.tanomis.com/v1/sessions",
headers={
"Authorization": f"Bearer {token}",
"X-Organization-ID": organization_id,
"X-Workspace-Tenant-ID": tenant_id,
},
json={
"agent_name": "support-copilot",
"idempotency_key": "support-case-1842",
},
)
response.raise_for_status()
result = response.json()
```
```typescript
const response = await fetch(`https://api.tanomis.com/v1/sessions`, {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"X-Organization-ID": organizationId,
"X-Workspace-Tenant-ID": tenantId,
"Content-Type": "application/json",
},
body: JSON.stringify({
"agent_name": "support-copilot",
"idempotency_key": "support-case-1842"
}),
});
if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);
const result = await response.json();
```
## Response
```json title="201 Created"
{
"id": "ses_7d4f9c",
"agent_name": "support-copilot",
"version": "0.4.0",
"status": "ready",
"model": "claude-sonnet",
"created_at": "2026-09-02T11:42:18Z",
"awaiting_input": false
}
```
---
Source: /api/sessions/get-session
# Read a session
GET /v1/sessions/{session_id}: Read one session, including `awaiting_input` and, where file attachments are enabled, `session_files_supported`.
GET/v1/sessions/{session_id}
Read one session, including `awaiting_input` and, where file attachments are enabled, `session_files_supported`.
Generated from the Platform API specification on 2026-09-04.
## Path parameters
| Parameter | Type | Description |
|---|---|---|
| `session_id` | string | The session, as returned by Create a session. |
## Headers
| Header | Required | Description |
|---|---|---|
| `Authorization` | yes | `Bearer` plus a short-lived OAuth access token issued for the Tanomis API audience. Backends obtain tokens with an [organization automation client](/guides/run/authenticate-your-backend); the CLI's human login is not an application credential. |
| `X-Organization-ID` | yes | The `organization_id` from the workspace projection you selected in `GET /v1/me/workspaces`. |
| `X-Workspace-Tenant-ID` | yes | The nested `tenant_id` from the same projection. A header cannot widen what the token grants. Some single-workspace or self-hosted environments take `X-Tenant-ID` instead, as supplied by their administrator. |
## Responses
| Status | Meaning |
|---|---|
| `200` | The session. |
| `404` | No such session in the selected tenant. |
## Request
```bash
curl -X GET "https://api.tanomis.com/v1/sessions/ses_7d4f9c" \
-H "Authorization: Bearer $TANOMIS_TOKEN" \
-H "X-Organization-ID: $TANOMIS_ORG" \
-H "X-Workspace-Tenant-ID: $TANOMIS_WORKSPACE"
```
```python
import httpx
response = httpx.get(
f"https://api.tanomis.com/v1/sessions/{session_id}",
headers={
"Authorization": f"Bearer {token}",
"X-Organization-ID": organization_id,
"X-Workspace-Tenant-ID": tenant_id,
},
)
response.raise_for_status()
result = response.json()
```
```typescript
const response = await fetch(`https://api.tanomis.com/v1/sessions/${sessionId}`, {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`,
"X-Organization-ID": organizationId,
"X-Workspace-Tenant-ID": tenantId,
},
});
if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);
const result = await response.json();
```
## Response
```json title="200 OK"
{
"id": "ses_7d4f9c",
"agent_name": "support-copilot",
"version": "0.4.0",
"status": "ready",
"model": "claude-sonnet",
"created_at": "2026-09-02T11:42:18Z",
"awaiting_input": false
}
```
---
Source: /api/sessions/list-sessions
# List sessions
GET /v1/sessions: List the caller's sessions in the selected tenant.
GET/v1/sessions
List the caller's sessions in the selected tenant. `include_all_users=true` returns the tenant-wide view permitted by the caller's role, inside the same tenant boundary.
Generated from the Platform API specification on 2026-09-04.
## Query parameters
| Parameter | Type | Description |
|---|---|---|
| `agent` | string | Only sessions of this agent. |
| `status` | string | Only sessions in this status. |
| `limit` | integer | Maximum number of sessions to return. |
| `parent_session_id` | string | Only sessions forked from this parent. |
| `include_all_users` | boolean | Include sessions owned by other users the caller's role may see. |
## Headers
| Header | Required | Description |
|---|---|---|
| `Authorization` | yes | `Bearer` plus a short-lived OAuth access token issued for the Tanomis API audience. Backends obtain tokens with an [organization automation client](/guides/run/authenticate-your-backend); the CLI's human login is not an application credential. |
| `X-Organization-ID` | yes | The `organization_id` from the workspace projection you selected in `GET /v1/me/workspaces`. |
| `X-Workspace-Tenant-ID` | yes | The nested `tenant_id` from the same projection. A header cannot widen what the token grants. Some single-workspace or self-hosted environments take `X-Tenant-ID` instead, as supplied by their administrator. |
## Responses
| Status | Meaning |
|---|---|
| `200` | Sessions, newest first. |
## Request
```bash
curl -X GET "https://api.tanomis.com/v1/sessions" \
-H "Authorization: Bearer $TANOMIS_TOKEN" \
-H "X-Organization-ID: $TANOMIS_ORG" \
-H "X-Workspace-Tenant-ID: $TANOMIS_WORKSPACE"
```
```python
import httpx
response = httpx.get(
f"https://api.tanomis.com/v1/sessions",
headers={
"Authorization": f"Bearer {token}",
"X-Organization-ID": organization_id,
"X-Workspace-Tenant-ID": tenant_id,
},
)
response.raise_for_status()
result = response.json()
```
```typescript
const response = await fetch(`https://api.tanomis.com/v1/sessions`, {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`,
"X-Organization-ID": organizationId,
"X-Workspace-Tenant-ID": tenantId,
},
});
if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);
const result = await response.json();
```
## Response
```json title="200 OK"
{
"sessions": [
{
"id": "ses_7d4f9c",
"agent_name": "support-copilot",
"version": "0.4.0",
"status": "ready",
"created_at": "2026-09-02T11:42:18Z",
"awaiting_input": false
}
]
}
```
---
Source: /api/sessions/resume-session
# Resume a session
POST /v1/sessions/{session_id}/resume: Restart a stopped, failed, or expired session.
POST/v1/sessions/{session_id}/resume
Restart a stopped, failed, or expired session. Suspended live sessions resume automatically when work arrives, so this is only needed after a terminal state.
Generated from the Platform API specification on 2026-09-04.
## Path parameters
| Parameter | Type | Description |
|---|---|---|
| `session_id` | string | The session, as returned by Create a session. |
## Headers
| Header | Required | Description |
|---|---|---|
| `Authorization` | yes | `Bearer` plus a short-lived OAuth access token issued for the Tanomis API audience. Backends obtain tokens with an [organization automation client](/guides/run/authenticate-your-backend); the CLI's human login is not an application credential. |
| `X-Organization-ID` | yes | The `organization_id` from the workspace projection you selected in `GET /v1/me/workspaces`. |
| `X-Workspace-Tenant-ID` | yes | The nested `tenant_id` from the same projection. A header cannot widen what the token grants. Some single-workspace or self-hosted environments take `X-Tenant-ID` instead, as supplied by their administrator. |
## Responses
| Status | Meaning |
|---|---|
| `200` | The session, ready again. |
| `404` | No such session in the selected tenant. |
| `409` | The session is not in a state that can be resumed. |
## Request
```bash
curl -X POST "https://api.tanomis.com/v1/sessions/ses_7d4f9c/resume" \
-H "Authorization: Bearer $TANOMIS_TOKEN" \
-H "X-Organization-ID: $TANOMIS_ORG" \
-H "X-Workspace-Tenant-ID: $TANOMIS_WORKSPACE"
```
```python
import httpx
response = httpx.post(
f"https://api.tanomis.com/v1/sessions/{session_id}/resume",
headers={
"Authorization": f"Bearer {token}",
"X-Organization-ID": organization_id,
"X-Workspace-Tenant-ID": tenant_id,
},
)
response.raise_for_status()
result = response.json()
```
```typescript
const response = await fetch(`https://api.tanomis.com/v1/sessions/${sessionId}/resume`, {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"X-Organization-ID": organizationId,
"X-Workspace-Tenant-ID": tenantId,
},
});
if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);
const result = await response.json();
```
## Response
```json title="200 OK"
{
"id": "ses_7d4f9c",
"agent_name": "support-copilot",
"version": "0.4.0",
"status": "ready",
"model": "claude-sonnet",
"created_at": "2026-09-02T11:42:18Z",
"awaiting_input": false
}
```
---
Source: /api/sessions/stop-session
# Stop a session
POST /v1/sessions/{session_id}/stop: Stop the session and release its active runtime.
POST/v1/sessions/{session_id}/stop
Stop the session and release its active runtime. Stop it when no more work will use it. A stopped session can be restarted with Resume a session.
Generated from the Platform API specification on 2026-09-04.
## Path parameters
| Parameter | Type | Description |
|---|---|---|
| `session_id` | string | The session, as returned by Create a session. |
## Headers
| Header | Required | Description |
|---|---|---|
| `Authorization` | yes | `Bearer` plus a short-lived OAuth access token issued for the Tanomis API audience. Backends obtain tokens with an [organization automation client](/guides/run/authenticate-your-backend); the CLI's human login is not an application credential. |
| `X-Organization-ID` | yes | The `organization_id` from the workspace projection you selected in `GET /v1/me/workspaces`. |
| `X-Workspace-Tenant-ID` | yes | The nested `tenant_id` from the same projection. A header cannot widen what the token grants. Some single-workspace or self-hosted environments take `X-Tenant-ID` instead, as supplied by their administrator. |
## Responses
| Status | Meaning |
|---|---|
| `200` | The session in its new state. |
| `404` | No such session in the selected tenant. |
## Request
```bash
curl -X POST "https://api.tanomis.com/v1/sessions/ses_7d4f9c/stop" \
-H "Authorization: Bearer $TANOMIS_TOKEN" \
-H "X-Organization-ID: $TANOMIS_ORG" \
-H "X-Workspace-Tenant-ID: $TANOMIS_WORKSPACE"
```
```python
import httpx
response = httpx.post(
f"https://api.tanomis.com/v1/sessions/{session_id}/stop",
headers={
"Authorization": f"Bearer {token}",
"X-Organization-ID": organization_id,
"X-Workspace-Tenant-ID": tenant_id,
},
)
response.raise_for_status()
result = response.json()
```
```typescript
const response = await fetch(`https://api.tanomis.com/v1/sessions/${sessionId}/stop`, {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"X-Organization-ID": organizationId,
"X-Workspace-Tenant-ID": tenantId,
},
});
if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);
const result = await response.json();
```
## Response
```json title="200 OK"
{
"id": "ses_7d4f9c",
"agent_name": "support-copilot",
"version": "0.4.0",
"status": "stopped",
"created_at": "2026-09-02T11:42:18Z",
"awaiting_input": false
}
```
---
Source: /api/workspaces/list-workspaces
# List workspaces
GET /v1/me/workspaces: Return only the workspace projections granted to the caller.
GET/v1/me/workspaces
Return only the workspace projections granted to the caller. Each combines an organization, an attached tenant, the caller's role, and effective capabilities. Copy `organization_id` and the selected nested `tenant_id` into the two workspace headers. Selecting the projection supplies context; the workspace does not own the tenant or its resources. An authenticated caller with no active organization membership receives `{ "workspaces": [] }`. A developer or viewer who belongs to an organization but has no granted tenant remains visible as that organization with `"tenants": []`; `tanomis workspace ls` renders its tenant as `(none)`. Owners and administrators see the organization's attached tenants.
Generated from the Platform API specification on 2026-09-04.
## Headers
| Header | Required | Description |
|---|---|---|
| `Authorization` | yes | `Bearer` plus a short-lived OAuth access token issued for the Tanomis API audience. Backends obtain tokens with an [organization automation client](/guides/run/authenticate-your-backend); the CLI's human login is not an application credential. |
| `X-Organization-ID` | yes | The `organization_id` from the workspace projection you selected in `GET /v1/me/workspaces`. |
| `X-Workspace-Tenant-ID` | yes | The nested `tenant_id` from the same projection. A header cannot widen what the token grants. Some single-workspace or self-hosted environments take `X-Tenant-ID` instead, as supplied by their administrator. |
## Responses
| Status | Meaning |
|---|---|
| `200` | The caller's projections. |
## Request
```bash
curl -X GET "https://api.tanomis.com/v1/me/workspaces" \
-H "Authorization: Bearer $TANOMIS_TOKEN" \
-H "X-Organization-ID: $TANOMIS_ORG" \
-H "X-Workspace-Tenant-ID: $TANOMIS_WORKSPACE"
```
```python
import httpx
response = httpx.get(
f"https://api.tanomis.com/v1/me/workspaces",
headers={
"Authorization": f"Bearer {token}",
"X-Organization-ID": organization_id,
"X-Workspace-Tenant-ID": tenant_id,
},
)
response.raise_for_status()
result = response.json()
```
```typescript
const response = await fetch(`https://api.tanomis.com/v1/me/workspaces`, {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`,
"X-Organization-ID": organizationId,
"X-Workspace-Tenant-ID": tenantId,
},
});
if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);
const result = await response.json();
```
## Response
```json title="200 OK"
{
"workspaces": [
{
"organization_id": "org_abc123",
"organization_slug": "northwind",
"organization_display_name": "Northwind",
"role": "developer",
"tenants": [
{
"tenant_id": "customer-production",
"state": "ready",
"is_default": true,
"capabilities": [
"run",
"view"
]
}
]
}
]
}
```
---
Source: /api/workspaces/whoami
# Inspect the caller
GET /v1/whoami: Confirm which identity and capabilities the access token resolved.
GET/v1/whoami
Confirm which identity and capabilities the access token resolved. `scopes` lists the caller's own granted API scopes; an empty array means none. `file_attachments_enabled` is environment-wide; a session's `session_files_supported` is the additional agent-specific check.
Generated from the Platform API specification on 2026-09-04.
## Headers
| Header | Required | Description |
|---|---|---|
| `Authorization` | yes | `Bearer` plus a short-lived OAuth access token issued for the Tanomis API audience. Backends obtain tokens with an [organization automation client](/guides/run/authenticate-your-backend); the CLI's human login is not an application credential. |
| `X-Organization-ID` | yes | The `organization_id` from the workspace projection you selected in `GET /v1/me/workspaces`. |
| `X-Workspace-Tenant-ID` | yes | The nested `tenant_id` from the same projection. A header cannot widen what the token grants. Some single-workspace or self-hosted environments take `X-Tenant-ID` instead, as supplied by their administrator. |
## Responses
| Status | Meaning |
|---|---|
| `200` | The resolved identity. |
## Request
```bash
curl -X GET "https://api.tanomis.com/v1/whoami" \
-H "Authorization: Bearer $TANOMIS_TOKEN" \
-H "X-Organization-ID: $TANOMIS_ORG" \
-H "X-Workspace-Tenant-ID: $TANOMIS_WORKSPACE"
```
```python
import httpx
response = httpx.get(
f"https://api.tanomis.com/v1/whoami",
headers={
"Authorization": f"Bearer {token}",
"X-Organization-ID": organization_id,
"X-Workspace-Tenant-ID": tenant_id,
},
)
response.raise_for_status()
result = response.json()
```
```typescript
const response = await fetch(`https://api.tanomis.com/v1/whoami`, {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`,
"X-Organization-ID": organizationId,
"X-Workspace-Tenant-ID": tenantId,
},
});
if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);
const result = await response.json();
```
## Response
```json title="200 OK"
{
"subject": "usr_84af2c",
"tenant_id": "customer-production",
"is_admin": false,
"scopes": [],
"file_attachments_enabled": true
}
```
---
Source: /changelog
# Changelog
Platform, CLI, manifest, and API changes that affect what you build on Tanomis, newest first.
Changes that affect what you build, newest first. Each entry names the surface it touches and
links to the page that describes it.
## September 2026
### 8 September
- **CLI.** Tanomis v0.2.0 renames the command to `tanomis`. The installer includes a deprecated
`cog` shim; existing `COG_*` settings and the legacy config directory remain supported.
New installs use `TANOMIS_*` settings and the Tanomis config directory. See
[Install Tanomis](/getting-started/install).
- **Docs and domains.** Developer docs and downloads move to `docs.tanomis.com` and
`get.tanomis.com`. Old Cogward docs and download links redirect to the same path on Tanomis;
previously published `cog/` release objects remain unchanged.
- **Coding agents.** Register the bundled docs MCP server with `tanomis skills install`, or run
it directly with `tanomis docs serve`. See [Use with coding agents](/getting-started/coding-agents).
- **CLI.** `cog` v0.1.1 adds the platform skills for coding agents and the documentation
server. `cog skills install` writes four skills (build, deploy and roll out, run and debug,
govern) for Claude Code, Codex, and Cursor and registers `cog docs serve`, an MCP server
over these docs. See [Build with a coding agent](/getting-started/coding-agents).
### 6 September
- **CLI.** `cog` v0.1.0 is the first public release. Install on macOS and Linux with
`curl -fsSL https://get.cogward.com/install.sh | sh`, on Windows with
`irm https://get.cogward.com/install.ps1 | iex`. Every release carries `SHA256SUMS` and a
keyless cosign signature. See [Install cog](/getting-started/install).
### 5 September
- **Platform.** Organization-owned automation credentials ship: create a machine client in the
Console or with `cog m2m create`, choose the `observe`, `invoke`, or `deploy` profile, rotate
and revoke. See [Automation credentials](/guides/deploy/automation-credentials) and
[Authenticate a backend](/guides/run/authenticate-your-backend).
- **CLI.** `cog m2m` manages machine clients; `cog agent deploy --scope global --tenants all`
registers an agent once and enables it for every tenant. See the [Quickstart](/getting-started/quickstart).
- **Docs.** The site is reorganized around the agent journey: a new Overview,
[Quickstart](/getting-started/quickstart), [Platform model](/concepts/platform-model), and
[What is available where](/reference/availability). Pages marked **Soon** describe capabilities
being built.
### 4 September
- **CLI.** `cog login` can select a workspace, and `cog workspace show` prints the selection.
See [Authenticate and select a workspace](/getting-started/authenticate-and-select-workspace).
- **Platform.** Durable questions and permission requests pause a run until a person answers
through the Platform API. See [Approval-required actions](/guides/run/approval-required-actions).
- **Platform.** Agent identity modes and the authorization contract are documented. See
[Identity and access](/concepts/identity-and-access).
### 2 September
- **API.** The customer Platform API reference is published: sessions, runs, event streaming,
human interactions, and session files. See [Platform API](/api).
## August 2026
### 29 August
- **Manifest.** `network.egress` declares the destinations a version may reach, bounded by the
tenant ceiling. See [Agent manifest](/reference/agent-manifest#network).
### 28 August
- **Manifest.** The complete `agent.yaml` field reference is published, including cross-field
rules and pre-build validation. See [Agent manifest](/reference/agent-manifest).
---
Source: /cli
# Journey commands
A focused reference for the tanomis commands used to authenticate, scaffold, deploy, inspect, and run.
This is the compact reference for the first developer journey. Run `tanomis --help` for the
complete flag set supported by your installed version.
## Install
```bash
curl -fsSL https://get.tanomis.com/install.sh | sh
```
One command on macOS and Linux; Windows uses `irm https://get.tanomis.com/install.ps1 | iex`. Pin
with `TANOMIS_VERSION`; see [Install tanomis](/getting-started/install).
## Authenticate
```bash
tanomis login --console https://api.tanomis.com --name saas
tanomis auth status
tanomis logout
```
`login --console` discovers the hosted configuration, creates or updates the named context, and
starts device authorization by default. `logout` clears cached credentials but keeps the context.
Follow [Authenticate and select a workspace](/getting-started/authenticate-and-select-workspace)
for the complete human login, context, workspace-discovery, selection, and failure flow.
## Discover and scaffold
```bash
tanomis agent harnesses
tanomis agent new NAME \
--harness claude-agents-sdk \
--agents-root ./agents \
--description "What this agent does"
```
`--harness` accepts a value reported by `agent harnesses`. `--agents-root` can point anywhere,
including an external repository. Scaffolding writes a self-contained project and runs `uv lock`
when `uv` is available.
## Validate and deploy
```bash
tanomis agent deploy PATH --tenant TENANT --dry-run
tanomis agent deploy PATH --tenant TENANT
tanomis agent deploy PATH --scope global --tenants all
```
`--tenant` deploys to one tenant. `--scope global --tenants all` registers a global agent once and
enables it for every active tenant; a platform operator's path.
Useful deploy flags:
| Flag | Meaning |
|---|---|
| `--dry-run` | Preflight and package locally; create no build and upload nothing. |
| `--version VERSION` | Override the version from `agent.yaml`. |
| `--no-wait` | Return after upload instead of waiting for terminal build status. |
| `--no-stream-logs` | Poll status without streaming build events. |
| `--output json` | Emit machine-readable output for automation. |
The supported remote build profile is `python-managed`. Remote builds require `uv.lock` or a
`requirements.txt`; pin every requirement to keep builds reproducible. The CLI verifies that the
file exists but does not inspect its entries. `--allow-unlocked` is not supported.
## Inspect
```bash
tanomis agent ls --tenant TENANT
tanomis agent get NAME --tenant TENANT
tanomis agent versions NAME --tenant TENANT
tanomis agent versions NAME --by-tenant
```
Use `get` for an agent-centric view and `versions --by-tenant` for the reverse rollout view.
## Roll out and roll back
```bash
tanomis agent rollout NAME --version VERSION --tenants TENANT --dry-run
tanomis agent rollout NAME --version VERSION --tenants TENANT --canary-share 10
tanomis agent versions NAME --by-tenant
```
`rollout` promotes the requested global version if needed, then assigns it only to the requested
tenants. Omit `--canary-share` for a baseline assignment; a value from 1 through 99 uses each
tenant's baseline observed at command start and adds the version as a canary. Serialize assignment
changes for each tenant so a cached baseline cannot overwrite a concurrent rollout. Treat output as
provisional: a tenant assignment failure can appear as success. Only a follow-up
`versions --by-tenant` read verifies the assignment. Roll back with named-tenant
`agent enable --baseline KNOWN_GOOD`; built-in deprecated versions remain serveable even though
`rollout` cannot transition them back to production. Require zero failed in the enable summary and
verify the version view. Comma-separated tenant batches require ordinary authority over every target;
support act-as assertions are tenant-bound, so run one tenant per command under its own assertion.
`agent disable` removes enablement and stops service; it does not select an older version.
Follow the [rollout and rollback tutorial](/guides/deploy/rollout-and-rollback) before changing customer
tenant assignments.
## Run
```bash
tanomis session run NAME \
--tenant TENANT \
--prompt "Your prompt"
```
`session run` creates a session, starts a run, and streams events through the managed runtime. Use
`--input '{"key":"value"}'` for raw JSON instead of `--prompt`; the two flags are mutually
exclusive. Use `--no-stream` to print the run ID without following events.
## Automate
```bash
tanomis m2m create ci-deployer --profile deploy
tanomis m2m ls
tanomis m2m rotate ci-deployer
```
`m2m create` prints the client ID and secret once. Set `TANOMIS_API_URL`, `TANOMIS_CLIENT_ID`, and
`TANOMIS_CLIENT_SECRET` in a job and every command above runs unattended; see
[Automation credentials](/guides/deploy/automation-credentials).
## Command-scoped context
The active context applies automatically. Select another existing context for one command without
switching:
```bash
tanomis agent ls --context staging --tenant TENANT
```
`--context` selects an existing context. `login --name` names the context created by
`login --console`.
---
Source: /cli/agent
# tanomis agent
Reference for tanomis agent: author, register, and inspect agents.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis agent --help for the version you have installed.
## Usage
```text title="Usage"
tanomis agent
```
## Description
Author, register, and inspect agents.
## Commands
| Command | Description |
|---|---|
| [`tanomis agent delete-local`](/cli/agent/delete-local) | delete a scaffolded agent directory from disk |
| [`tanomis agent deploy`](/cli/agent/deploy) | package, upload, and remotely build an agent from local source |
| [`tanomis agent disable`](/cli/agent/disable) | remove an agent's enablement for specific tenants (inverse of enable) |
| [`tanomis agent enable`](/cli/agent/enable) | enable a global agent for tenants |
| [`tanomis agent get`](/cli/agent/get) | show the agent and versions served in the selected workspace |
| [`tanomis agent harnesses`](/cli/agent/harnesses) | list available agent harnesses |
| [`tanomis agent inventory`](/cli/agent/inventory) | list the per-version inventory (AI-BOM) of the agents a tenant can see |
| [`tanomis agent ls`](/cli/agent/ls) | list registered agents (or, with `--tenant`, the agents a tenant can run) |
| [`tanomis agent new`](/cli/agent/new) | scaffold a new agent from a harness template |
| [`tanomis agent promote`](/cli/agent/promote) | promote a version to production (alias for transition `--to` production) |
| [`tanomis agent rm`](/cli/agent/rm) | soft-retire one scope-qualified agent from the registry |
| [`tanomis agent rollout`](/cli/agent/rollout) | roll a version out to specific tenants (promote-if-needed, then enable) |
| [`tanomis agent statuses`](/cli/agent/statuses) | show the version lifecycle: statuses and their allowed transitions |
| [`tanomis agent versions`](/cli/agent/versions) | list an agent's version lineage (label, id, status, kind) |
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/agent/delete-local
# tanomis agent delete-local
Reference for tanomis agent delete-local: delete a scaffolded agent directory from disk.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis agent delete-local --help for the version you have installed.
## Usage
```text title="Usage"
tanomis agent delete-local NAME [flags]
```
## Description
Delete a scaffolded agent directory from disk.
## Flags
| Flag | Type | Description |
|---|---|---|
| `--agents-root` | string | Local agents directory. Default `dev/agents`. |
| `--yes` | bool | Skip the confirmation prompt. |
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/agent/deploy
# tanomis agent deploy
Reference for tanomis agent deploy: package, upload, and remotely build an agent from local source.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis agent deploy --help for the version you have installed.
## Usage
```text title="Usage"
tanomis agent deploy [PATH] [flags]
```
## Description
Turn local Python agent source into a remote build (feature 260703d1d).
Preflights PATH (default "."): parses agent.yaml, requires a harness (framework),
rejects a stray Dockerfile or an unsupported top-level build field, and flags
secret-looking files (.env, *.pem, *.key, or a name containing "credentials") — those
are excluded from the package as well as warned about. Packages PATH deterministically
(a repackage of the same tree yields an identical checksum), enforces the Python lock
policy (a remote build requires a uv.lock or a pinned requirements.txt; a bare
pyproject.toml is rejected), then creates a build job, uploads the bundle, and streams
its progress to the terminal.
`--dry-run` stops after packaging and prints the plan: no build is created and no bytes
are uploaded. Public egress additionally performs an authenticated read-only ceiling validation.
`--stage` builds and registers the version, ready to start sessions, and enables it for
nobody: the tenant keeps the version it serves. Test it with `tanomis eval ci`, then
release it with `tanomis agent promote`, naming the version and the tenant.
## Flags
| Flag | Type | Description |
|---|---|---|
| `--allow-unlocked` | bool | Not supported for a remote build: the build service requires a uv.lock or a pinned requirements.txt (using this flag now returns a clear error rather than failing the remote build). |
| `--build-profile` | string | Build profile (only python-managed is supported in v1). Default `python-managed`. |
| `--dry-run` | bool | Preflight and package without creating a build or uploading source; public egress validates the ceiling. |
| `--force-rebuild` | bool | Reserved for a future server-side build cache; currently a no-op. |
| `--name` | string | Agent name (default: agent.yaml's name). |
| `--no-stream-logs` | bool | Disable streaming build events (poll for terminal status instead). |
| `--no-wait` | bool | Return immediately after the source uploads, without waiting for the build. |
| `--scope` | string | Registration scope: organization, workspace or platform (default: organization where the platform enables it, else workspace). |
| `--stage` | bool | Build and register the version without enabling it for the tenant (test it before release). |
| `--stream-logs` | bool | Stream build events to the terminal (default: on when stdout is a terminal). Default `true`. |
| `--tenants` | string | Platform deployment targets: all or a comma-separated tenant list (requires `--scope` platform). |
| `--version` | string | Version label (default: agent.yaml's version). |
| `--wait` | bool | Wait for the build to reach a terminal status before exiting. Default `true`. |
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
## Examples
---
Source: /cli/agent/disable
# tanomis agent disable
Reference for tanomis agent disable: remove an agent's enablement for specific tenants (inverse of enable).
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis agent disable --help for the version you have installed.
## Usage
```text title="Usage"
tanomis agent disable NAME [flags]
```
## Description
Stop serving an agent to the named tenants by removing their enablement rows — the
inverse of `tanomis agent enable`.
`--tenants` is a comma-separated list. There is no 'all' (removing every tenant's
enablement must be explicit). A tenant that isn't enabled is reported and skipped, so
re-running is safe (idempotent). The agent and its versions are untouched; only the
per-tenant enablement is removed (an audit event is recorded).
## Flags
| Flag | Type | Description |
|---|---|---|
| `--by` | string | Identity recorded as the principal. Default `local-dev`. |
| `--tenants` | string | Comma-separated tenant ids to disable (no 'all'). |
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/agent/enable
# tanomis agent enable
Reference for tanomis agent enable: enable a global agent for tenants.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis agent enable --help for the version you have installed.
## Usage
```text title="Usage"
tanomis agent enable NAME [flags]
```
## Description
Enable one global agent across a set of tenants in a single call.
`--tenants` takes a comma-separated list of tenant ids, or 'all' for every tenant.
## Flags
| Flag | Type | Description |
|---|---|---|
| `--baseline` | string | Baseline version to enable — label (v1) or id (ver_…). |
| `--canary` | string | Optional canary version — label (v1) or id (ver_…). |
| `--canary-share` | int | Canary traffic share (0-100). |
| `--tenants` | string | Comma-separated tenant ids, or 'all'. |
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/agent/get
# tanomis agent get
Reference for tanomis agent get: show the agent and versions served in the selected workspace.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis agent get --help for the version you have installed.
## Usage
```text title="Usage"
tanomis agent get NAME
```
## Description
Show the registration and baseline/canary versions served in the selected workspace.
In the platform context, show the platform registration, full version lineage, and tenant enablements.
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/agent/harnesses
# tanomis agent harnesses
Reference for tanomis agent harnesses: list available agent harnesses.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis agent harnesses --help for the version you have installed.
## Usage
```text title="Usage"
tanomis agent harnesses
```
## Description
List available agent harnesses.
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/agent/inventory
# tanomis agent inventory
Reference for tanomis agent inventory: list the per-version inventory (AI-BOM) of the agents a tenant can see.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis agent inventory --help for the version you have installed.
## Usage
```text title="Usage"
tanomis agent inventory [flags]
```
## Description
List every agent version the tenant can see with its models, image digest, harness,
integrations, tools, golden fingerprint and recorded signature and build provenance.
A tenant-owned agent lists its whole lineage; a global agent lists only the versions the
tenant is served. A field the registry does not record is null. With `--output` json the
output is an export document stamped with schema_version, generated_at and tenant.
## Flags
| Flag | Type | Description |
|---|---|---|
| `--tenant` | string | Tenant whose inventory to list (default: the selected workspace). |
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/agent/ls
# tanomis agent ls
Reference for tanomis agent ls: list registered agents (or, with --tenant, the agents a tenant can run).
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis agent ls --help for the version you have installed.
## Usage
```text title="Usage"
tanomis agent ls [flags]
```
## Description
List registered agents (or, with `--tenant`, the agents a tenant can run).
Aliases: `list`.
## Flags
| Flag | Type | Description |
|---|---|---|
| `--tenant` | string | Show the agents this tenant can run (global-enabled + tenant-scoped), with served version. |
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/agent/new
# tanomis agent new
Reference for tanomis agent new: scaffold a new agent from a harness template.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis agent new --help for the version you have installed.
## Usage
```text title="Usage"
tanomis agent new NAME [flags]
```
## Description
Scaffold a new agent from a harness template into ``/``.
`--agents-root` defaults to dev/agents (the monorepo convention) but accepts any path, absolute or relative, including a directory outside this repo (e.g. an external agent repo cloned on its own). The generated agent is self-contained (agent.yaml, Python source, pyproject.toml, README, .gitignore, .tanomisignore) and has no monorepo-only assumptions, so it deploys with 'tanomis agent deploy' unmodified.
The manifest is production-ready as generated: it carries a live authorization contract (a scope_ceiling declaring the agent's own model, plus allowed_audiences). Golden snapshots are platform-managed to accelerate startup. Dependencies are locked with uv when it is available, which a remote build requires.
## Flags
| Flag | Type | Description |
|---|---|---|
| `--agents-root` | string | Target agents directory; any path, including one outside this repo. Default `dev/agents`. |
| `--description` | string | Manifest description. |
| `--harness` | string | Agent harness/framework. Default `claude-agents-sdk`. |
| `--model` | string | Model id. |
| `--overwrite` | bool | Overwrite an existing agent directory. |
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
## Examples
---
Source: /cli/agent/promote
# tanomis agent promote
Reference for tanomis agent promote: promote a version to production (alias for transition --to production).
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis agent promote --help for the version you have installed.
## Usage
```text title="Usage"
tanomis agent promote NAME [flags]
```
## Description
Promote a version to production (alias for transition `--to` production).
## Flags
| Flag | Type | Description |
|---|---|---|
| `--by` | string | Identity recorded as the principal. Default `local-dev`. |
| `--scope-tenant` | string | Select the tenant-scoped agent of this name (default: the global agent). |
| `--version` | string | Version label to promote. |
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/agent/rm
# tanomis agent rm
Reference for tanomis agent rm: soft-retire one scope-qualified agent from the registry.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis agent rm --help for the version you have installed.
## Usage
```text title="Usage"
tanomis agent rm NAME [flags]
```
## Description
Soft-retire exactly one agent registration while preserving its audit history.
The scope selector is mandatory because global and tenant-scoped agents may share a name.
Use `--scope` global, or `--scope` tenant with `--scope-tenant-id` TENANT.
Aliases: `remove`.
## Flags
| Flag | Type | Description |
|---|---|---|
| `--scope` | string | Registration scope to retire: global or tenant (required). |
| `--scope-tenant-id` | string | Owning tenant id (required with `--scope` tenant). |
| `--yes` | bool | Skip the confirmation prompt. |
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/agent/rollout
# tanomis agent rollout
Reference for tanomis agent rollout: roll a version out to specific tenants (promote-if-needed, then enable).
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis agent rollout --help for the version you have installed.
## Usage
```text title="Usage"
tanomis agent rollout NAME [flags]
```
## Description
Roll one version out to an explicit set of tenants in a single, tenant-scoped step.
rollout composes the two underlying operations so the operator's intent — "serve
version V to these tenants" — is one command: it first ensures the version is at the
production status (transitioning it if needed, surfacing any gate failure verbatim),
then enables it for the named tenants only. By default the version becomes the tenants'
baseline; with `--canary-share` N (1-99) it is added as a canary at that share, preserving
each tenant's existing baseline. Other tenants are never touched.
Use `--dry-run` to preview the per-tenant before/after plan without changing anything.
This is porcelain over `tanomis agent promote` + `tanomis agent enable`, which remain available.
## Flags
| Flag | Type | Description |
|---|---|---|
| `--by` | string | Identity recorded as the principal. Default `local-dev`. |
| `--canary-share` | int | 0 = baseline (default); 1-99 = roll out as canary at this share. |
| `--dry-run` | bool | Print the per-tenant before/after plan without making changes. |
| `--tenants` | string | Comma-separated tenant ids, or 'all'. |
| `--version` | string | Version label to roll out (e.g. v2). |
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
## Examples
---
Source: /cli/agent/statuses
# tanomis agent statuses
Reference for tanomis agent statuses: show the version lifecycle: statuses and their allowed transitions.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis agent statuses --help for the version you have installed.
## Usage
```text title="Usage"
tanomis agent statuses
```
## Description
Show the promotion state machine — every version status, the statuses it can transition
to (what `tanomis agent transition `--to`` accepts), and whether each gates promotion, serves
traffic, or is terminal. Includes built-in and any operator-added custom statuses.
Aliases: `lifecycle`.
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/agent/versions
# tanomis agent versions
Reference for tanomis agent versions: list an agent's version lineage (label, id, status, kind).
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis agent versions --help for the version you have installed.
## Usage
```text title="Usage"
tanomis agent versions NAME [flags]
```
## Description
List an agent's immutable version lineage.
With `--by-tenant`, render the version-centric rollout view instead: for each version,
the tenants that serve it as baseline and as canary. This is the reverse of
`tanomis agent get` (agent→tenants) and `tanomis agent ls `--tenant`` (tenant→agents), and answers
"who is on v2?" directly.
## Flags
| Flag | Type | Description |
|---|---|---|
| `--by-tenant` | bool | Show, per version, which tenants serve it as baseline/canary. |
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/auth
# tanomis auth
Reference for tanomis auth: inspect authentication state.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis auth --help for the version you have installed.
## Usage
```text title="Usage"
tanomis auth
```
## Description
Inspect authentication state.
## Commands
| Command | Description |
|---|---|
| [`tanomis auth status`](/cli/auth/status) | show the active context's auth state |
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/auth/status
# tanomis auth status
Reference for tanomis auth status: show the active context's auth state.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis auth status --help for the version you have installed.
## Usage
```text title="Usage"
tanomis auth status
```
## Description
Show the active context's auth state.
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/config
# tanomis config
Reference for tanomis config: manage tanomis configuration and contexts.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis config --help for the version you have installed.
## Usage
```text title="Usage"
tanomis config
```
## Description
Manage tanomis configuration and contexts.
## Commands
| Command | Description |
|---|---|
| [`tanomis config current-context`](/cli/config/current-context) | print the active context name |
| [`tanomis config delete-context`](/cli/config/delete-context) | remove a context and clear its cached credentials |
| [`tanomis config use-context`](/cli/config/use-context) | set the active context |
| [`tanomis config view`](/cli/config/view) | show the effective configuration |
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/config/current-context
# tanomis config current-context
Reference for tanomis config current-context: print the active context name.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis config current-context --help for the version you have installed.
## Usage
```text title="Usage"
tanomis config current-context
```
## Description
Print the active context name.
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/config/delete-context
# tanomis config delete-context
Reference for tanomis config delete-context: remove a context and clear its cached credentials.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis config delete-context --help for the version you have installed.
## Usage
```text title="Usage"
tanomis config delete-context NAME
```
## Description
Remove a context from the config file and clear its cached credentials.
If NAME is the active context, the current-context is unset and you are
prompted to select a new one with 'tanomis config use-context'.
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/config/use-context
# tanomis config use-context
Reference for tanomis config use-context: set the active context.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis config use-context --help for the version you have installed.
## Usage
```text title="Usage"
tanomis config use-context NAME
```
## Description
Set the active context.
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/config/view
# tanomis config view
Reference for tanomis config view: show the effective configuration.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis config view --help for the version you have installed.
## Usage
```text title="Usage"
tanomis config view
```
## Description
Show the effective configuration.
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/docs
# tanomis docs
Reference for tanomis docs: serve the documentation to coding agents.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis docs --help for the version you have installed.
## Usage
```text title="Usage"
tanomis docs
```
## Description
Serve the documentation to coding agents.
## Commands
| Command | Description |
|---|---|
| [`tanomis docs serve`](/cli/docs/serve) | serve the documentation to a coding agent as MCP tools over stdio |
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/docs/serve
# tanomis docs serve
Reference for tanomis docs serve: serve the documentation to a coding agent as MCP tools over stdio.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis docs serve --help for the version you have installed.
## Usage
```text title="Usage"
tanomis docs serve [flags]
```
## Description
Runs a Model Context Protocol server on standard input and output with three tools: search_docs, read_doc, and list_docs. The documentation is the public site as one file, fetched on first use and cached; it is revalidated when the copy is older than an hour and served from the cache when the site cannot be reached. No credential is used. Register it with `skills install`, or point your agent at this command directly.
## Flags
| Flag | Type | Description |
|---|---|---|
| `--cache-dir` | string | Directory for the cached copy (default: docs-cache beside the CLI config file). |
| `--docs-url` | string | Documentation site to serve. Default `https://docs.tanomis.com`. |
| `--refresh` | bool | Ask the site for a fresh copy on first use even if the cache is recent. |
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/login
# tanomis login
Reference for tanomis login: log in to a hosted control plane via OIDC.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis login --help for the version you have installed.
## Usage
```text title="Usage"
tanomis login [flags]
```
## Description
Authenticate an operator against a hosted control plane and cache a
refreshable token. Defaults to the device authorization grant; use
`--loopback` for the browser authorization-code flow (requires an IdP
app that allows it). With `--console`, bootstrap the context from the
control plane's GET /v1/cli-config first.
## Flags
| Flag | Type | Description |
|---|---|---|
| `--ca-bundle` | string | Extra CA bundle (PEM) to trust for the IdP. |
| `--console` | string | Control-plane console/runtime base URL to bootstrap from. |
| `--device` | bool | Use the device authorization grant (default). |
| `--loopback` | bool | Use the browser authorization-code + PKCE loopback flow (requires an IdP app that allows it). |
| `--name` | string | Context name to create/update (default: console host). |
| `--tenant` | string | Preferred tenant/org (reserved; passthrough). |
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--verbose` | bool | Verbose output. |
## Examples
---
Source: /cli/logout
# tanomis logout
Reference for tanomis logout: clear cached credentials for the active (or --context) context.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis logout --help for the version you have installed.
## Usage
```text title="Usage"
tanomis logout
```
## Description
Clear cached credentials for the active (or `--context`) context.
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/m2m
# tanomis m2m
Reference for tanomis m2m: manage organization machine clients (human owner/admin login required).
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis m2m --help for the version you have installed.
## Usage
```text title="Usage"
tanomis m2m
```
## Description
Manage organization machine clients (human owner/admin login required).
## Commands
| Command | Description |
|---|---|
| [`tanomis m2m create`](/cli/m2m/create) | create a client and show its secret once |
| [`tanomis m2m delete`](/cli/m2m/delete) | delete a client and its credentials |
| [`tanomis m2m disable`](/cli/m2m/disable) | disable a client at the next authorization decision |
| [`tanomis m2m enable`](/cli/m2m/enable) | re-enable a client |
| [`tanomis m2m get`](/cli/m2m/get) | inspect a client and its credential metadata |
| [`tanomis m2m ls`](/cli/m2m/ls) | list machine clients |
| [`tanomis m2m permissions`](/cli/m2m/permissions) | change a client's permission profile or scopes |
| [`tanomis m2m rename`](/cli/m2m/rename) | rename a client |
| [`tanomis m2m revoke`](/cli/m2m/revoke) | revoke a credential |
| [`tanomis m2m rotate`](/cli/m2m/rotate) | create an overlapping credential and show its secret once |
## Flags
| Flag | Type | Description |
|---|---|---|
| `--org` | string | Organization ID or slug (default: your only organization). |
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/m2m/create
# tanomis m2m create
Reference for tanomis m2m create: create a client and show its secret once.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis m2m create --help for the version you have installed.
## Usage
```text title="Usage"
tanomis m2m create NAME [flags]
```
## Description
Create a client and show its secret once.
## Flags
| Flag | Type | Description |
|---|---|---|
| `--expires-in-days` | int | Credential lifetime (1–365 days). Default `90`. |
| `--profile` | string | Permission profile: observe, invoke, deploy. Default `observe`. |
| `--request-id` | string | Stable request ID for safe retries; secrets cannot be replayed. |
| `--scopes` | stringSlice | Custom comma-separated scopes. |
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/m2m/delete
# tanomis m2m delete
Reference for tanomis m2m delete: delete a client and its credentials.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis m2m delete --help for the version you have installed.
## Usage
```text title="Usage"
tanomis m2m delete CLIENT
```
## Description
Delete a client and its credentials.
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/m2m/disable
# tanomis m2m disable
Reference for tanomis m2m disable: disable a client at the next authorization decision.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis m2m disable --help for the version you have installed.
## Usage
```text title="Usage"
tanomis m2m disable CLIENT
```
## Description
Disable a client at the next authorization decision.
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/m2m/enable
# tanomis m2m enable
Reference for tanomis m2m enable: re-enable a client.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis m2m enable --help for the version you have installed.
## Usage
```text title="Usage"
tanomis m2m enable CLIENT
```
## Description
Re-enable a client.
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/m2m/get
# tanomis m2m get
Reference for tanomis m2m get: inspect a client and its credential metadata.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis m2m get --help for the version you have installed.
## Usage
```text title="Usage"
tanomis m2m get CLIENT
```
## Description
Inspect a client and its credential metadata.
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/m2m/ls
# tanomis m2m ls
Reference for tanomis m2m ls: list machine clients.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis m2m ls --help for the version you have installed.
## Usage
```text title="Usage"
tanomis m2m ls
```
## Description
List machine clients.
Aliases: `list`.
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/m2m/permissions
# tanomis m2m permissions
Reference for tanomis m2m permissions: change a client's permission profile or scopes.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis m2m permissions --help for the version you have installed.
## Usage
```text title="Usage"
tanomis m2m permissions CLIENT [flags]
```
## Description
Change a client's permission profile or scopes.
## Flags
| Flag | Type | Description |
|---|---|---|
| `--profile` | string | Permission profile: observe, invoke, deploy. Default `observe`. |
| `--scopes` | stringSlice | Custom comma-separated scopes. |
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/m2m/rename
# tanomis m2m rename
Reference for tanomis m2m rename: rename a client.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis m2m rename --help for the version you have installed.
## Usage
```text title="Usage"
tanomis m2m rename CLIENT NAME
```
## Description
Rename a client.
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/m2m/revoke
# tanomis m2m revoke
Reference for tanomis m2m revoke: revoke a credential.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis m2m revoke --help for the version you have installed.
## Usage
```text title="Usage"
tanomis m2m revoke CLIENT CREDENTIAL
```
## Description
Revoke a credential.
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/m2m/rotate
# tanomis m2m rotate
Reference for tanomis m2m rotate: create an overlapping credential and show its secret once.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis m2m rotate --help for the version you have installed.
## Usage
```text title="Usage"
tanomis m2m rotate CLIENT [flags]
```
## Description
Create an overlapping credential and show its secret once.
## Flags
| Flag | Type | Description |
|---|---|---|
| `--expires-in-days` | int | Credential lifetime (1–365 days). Default `90`. |
| `--label` | string | Credential label. Default `rotation`. |
| `--request-id` | string | Stable request ID for safe retries; secrets cannot be replayed. |
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/mcp
# tanomis mcp
Reference for tanomis mcp: manage governed MCP connectors.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis mcp --help for the version you have installed.
## Usage
```text title="Usage"
tanomis mcp
```
## Description
Manage governed MCP connectors.
## Commands
| Command | Description |
|---|---|
| [`tanomis mcp connector`](/cli/mcp/connector) | create, inspect, and test MCP connectors |
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/mcp/connector
# tanomis mcp connector
Reference for tanomis mcp connector: create, inspect, and test MCP connectors.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis mcp connector --help for the version you have installed.
## Usage
```text title="Usage"
tanomis mcp connector
```
## Description
Create, inspect, and test MCP connectors.
## Commands
| Command | Description |
|---|---|
| [`tanomis mcp connector create`](/cli/mcp/connector/create) | create an MCP connector |
| [`tanomis mcp connector discover`](/cli/mcp/connector/discover) | discover tools and print the inventory diff |
| [`tanomis mcp connector get`](/cli/mcp/connector/get) | show one MCP connector |
| [`tanomis mcp connector list`](/cli/mcp/connector/list) | list MCP connectors |
| [`tanomis mcp connector review`](/cli/mcp/connector/review) | set review status for one discovered MCP tool |
| [`tanomis mcp connector set-status`](/cli/mcp/connector/set-status) | set a connector's lifecycle status (draft\|active\|degraded\|disabled\|retired) |
| [`tanomis mcp connector test`](/cli/mcp/connector/test) | probe connector reachability and tools/list |
| [`tanomis mcp connector tools`](/cli/mcp/connector/tools) | list discovered tools for an MCP connector |
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/mcp/connector/create
# tanomis mcp connector create
Reference for tanomis mcp connector create: create an MCP connector.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis mcp connector create --help for the version you have installed.
## Usage
```text title="Usage"
tanomis mcp connector create [flags]
```
## Description
Create an MCP connector.
## Flags
| Flag | Type | Description |
|---|---|---|
| `--auth-mode` | string | Backend auth mode. Default `none`. |
| `--by` | string | Identity recorded as creator. Default `local-dev`. |
| `--ca-bundle-ref` | string | Trust bundle reference. |
| `--credential-ref` | string | Connector credential reference. |
| `--kind` | string | Connector kind. Default `remote_http`. |
| `--name` | string | Connector name. |
| `--network-mode` | string | Network mode. Default `public`. |
| `--scope` | string | Scope: tenant or global. Default `tenant`. |
| `--status` | string | Connector status. Default `draft`. |
| `--target-url` | string | Absolute http(s) MCP endpoint URL. |
| `--tenant-id` | string | Tenant id for tenant-scoped connectors. |
| `--transport` | string | MCP transport. Default `streamable_http`. |
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/mcp/connector/discover
# tanomis mcp connector discover
Reference for tanomis mcp connector discover: discover tools and print the inventory diff.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis mcp connector discover --help for the version you have installed.
## Usage
```text title="Usage"
tanomis mcp connector discover CONNECTOR_ID
```
## Description
Discover tools and print the inventory diff.
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/mcp/connector/get
# tanomis mcp connector get
Reference for tanomis mcp connector get: show one MCP connector.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis mcp connector get --help for the version you have installed.
## Usage
```text title="Usage"
tanomis mcp connector get CONNECTOR_ID
```
## Description
Show one MCP connector.
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/mcp/connector/list
# tanomis mcp connector list
Reference for tanomis mcp connector list: list MCP connectors.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis mcp connector list --help for the version you have installed.
## Usage
```text title="Usage"
tanomis mcp connector list [flags]
```
## Description
List MCP connectors.
## Flags
| Flag | Type | Description |
|---|---|---|
| `--tenant-id` | string | Tenant id used to include tenant-scoped connectors. |
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/mcp/connector/review
# tanomis mcp connector review
Reference for tanomis mcp connector review: set review status for one discovered MCP tool.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis mcp connector review --help for the version you have installed.
## Usage
```text title="Usage"
tanomis mcp connector review CONNECTOR_ID TOOL_NAME [flags]
```
## Description
Set review status for one discovered MCP tool.
## Flags
| Flag | Type | Description |
|---|---|---|
| `--by` | string | Identity recorded as reviewer. Default `local-dev`. |
| `--status` | string | Review status: approved, rejected, or pending. Default `approved`. |
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/mcp/connector/set-status
# tanomis mcp connector set-status
Reference for tanomis mcp connector set-status: set a connector's lifecycle status (draft|active|degraded|disabled|retired).
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis mcp connector set-status --help for the version you have installed.
## Usage
```text title="Usage"
tanomis mcp connector set-status CONNECTOR_ID STATUS [flags]
```
## Description
Set a connector's lifecycle status through the registry's validated transition route.
The server is the only authorization opinion (manage:mcp-connectors or the armed workspace
capability) and enforces the transition rules; the CLI only refuses values outside the
vocabulary so a typo fails before a network call. The primary use is activating a delegated
connector: probe and discover never change a per-user connector's status, so activation is an
explicit operator action rather than a side effect of a health check.
disabled and retired take the connector away from every member and, where the gateway
provisioner is armed, tear down its data-plane routes — so both require `--yes`, like the other
verbs whose mistakes are felt by someone else.
## Flags
| Flag | Type | Description |
|---|---|---|
| `--yes` | bool | Confirm a disabled/retired transition without prompting. |
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/mcp/connector/test
# tanomis mcp connector test
Reference for tanomis mcp connector test: probe connector reachability and tools/list.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis mcp connector test --help for the version you have installed.
## Usage
```text title="Usage"
tanomis mcp connector test CONNECTOR_ID
```
## Description
Probe connector reachability and tools/list.
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/mcp/connector/tools
# tanomis mcp connector tools
Reference for tanomis mcp connector tools: list discovered tools for an MCP connector.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis mcp connector tools --help for the version you have installed.
## Usage
```text title="Usage"
tanomis mcp connector tools CONNECTOR_ID
```
## Description
List discovered tools for an MCP connector.
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/org/tenants
# tanomis org tenants
Reference for tanomis org tenants: provision and manage your organization's tenants.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis org tenants --help for the version you have installed.
## Usage
```text title="Usage"
tanomis org tenants
```
## Description
Provision and manage your organization's tenants.
## Commands
| Command | Description |
|---|---|
| [`tanomis org tenants create`](/cli/org/tenants/create) | provision a tenant in this organization |
| [`tanomis org tenants get`](/cli/org/tenants/get) | inspect a tenant |
| [`tanomis org tenants ls`](/cli/org/tenants/ls) | list organization tenants |
| [`tanomis org tenants offboard`](/cli/org/tenants/offboard) | withdraw access and tear down a tenant's resources |
| [`tanomis org tenants retry`](/cli/org/tenants/retry) | retry failed provisioning |
## Flags
| Flag | Type | Description |
|---|---|---|
| `--org` | string | Organization ID or slug (default: your only organization). |
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/org/tenants/create
# tanomis org tenants create
Reference for tanomis org tenants create: provision a tenant in this organization.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis org tenants create --help for the version you have installed.
## Usage
```text title="Usage"
tanomis org tenants create NAME [flags]
```
## Description
Provision a tenant in this organization.
## Flags
| Flag | Type | Description |
|---|---|---|
| `--mode` | string | Saas\|enterprise-byo\|enterprise-managed. Default `saas`. |
| `--request-id` | string | Stable request ID to safely retry tenant creation. |
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/org/tenants/get
# tanomis org tenants get
Reference for tanomis org tenants get: inspect a tenant.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis org tenants get --help for the version you have installed.
## Usage
```text title="Usage"
tanomis org tenants get TENANT
```
## Description
Inspect a tenant.
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/org/tenants/ls
# tanomis org tenants ls
Reference for tanomis org tenants ls: list organization tenants.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis org tenants ls --help for the version you have installed.
## Usage
```text title="Usage"
tanomis org tenants ls
```
## Description
List organization tenants.
Aliases: `list`.
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/org/tenants/offboard
# tanomis org tenants offboard
Reference for tanomis org tenants offboard: withdraw access and tear down a tenant's resources.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis org tenants offboard --help for the version you have installed.
## Usage
```text title="Usage"
tanomis org tenants offboard TENANT [flags]
```
## Description
Withdraw access and tear down a tenant's resources.
## Flags
| Flag | Type | Description |
|---|---|---|
| `--confirm` | string | Repeat the tenant ID to confirm resource teardown. |
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/org/tenants/retry
# tanomis org tenants retry
Reference for tanomis org tenants retry: retry failed provisioning.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis org tenants retry --help for the version you have installed.
## Usage
```text title="Usage"
tanomis org tenants retry TENANT
```
## Description
Retry failed provisioning.
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/session
# tanomis session
Reference for tanomis session: run, observe, stop, and reap runtime sessions.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis session --help for the version you have installed.
## Usage
```text title="Usage"
tanomis session
```
## Description
Run, observe, stop, and reap runtime sessions.
## Commands
| Command | Description |
|---|---|
| [`tanomis session file`](/cli/session/file) | manage files in an existing session |
| [`tanomis session get`](/cli/session/get) | show one session |
| [`tanomis session ls`](/cli/session/ls) | list sessions |
| [`tanomis session run`](/cli/session/run) | invoke an agent: create a session, start a run, and stream its output |
| [`tanomis session stop`](/cli/session/stop) | stop a running session |
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/session/file
# tanomis session file
Reference for tanomis session file: manage files in an existing session.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis session file --help for the version you have installed.
## Usage
```text title="Usage"
tanomis session file
```
## Description
Manage files in an existing session.
## Commands
| Command | Description |
|---|---|
| [`tanomis session file get`](/cli/session/file/get) | download a session file |
| [`tanomis session file ls`](/cli/session/file/ls) | list session files |
| [`tanomis session file put`](/cli/session/file/put) | upload a file without starting a run |
| [`tanomis session file rm`](/cli/session/file/rm) | delete a session file |
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/session/file/get
# tanomis session file get
Reference for tanomis session file get: download a session file.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis session file get --help for the version you have installed.
## Usage
```text title="Usage"
tanomis session file get SESSION FILE OUTPUT
```
## Description
Download a session file.
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/session/file/ls
# tanomis session file ls
Reference for tanomis session file ls: list session files.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis session file ls --help for the version you have installed.
## Usage
```text title="Usage"
tanomis session file ls SESSION
```
## Description
List session files.
Aliases: `list`.
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/session/file/put
# tanomis session file put
Reference for tanomis session file put: upload a file without starting a run.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis session file put --help for the version you have installed.
## Usage
```text title="Usage"
tanomis session file put SESSION PATH [flags]
```
## Description
Upload a file without starting a run.
## Flags
| Flag | Type | Description |
|---|---|---|
| `--media-type` | string | Override media type (otherwise inferred from extension). |
| `--upload-key` | string | Idempotency key for prepare retries. |
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/session/file/rm
# tanomis session file rm
Reference for tanomis session file rm: delete a session file.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis session file rm --help for the version you have installed.
## Usage
```text title="Usage"
tanomis session file rm SESSION FILE
```
## Description
Delete a session file.
Aliases: `remove`.
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/session/get
# tanomis session get
Reference for tanomis session get: show one session.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis session get --help for the version you have installed.
## Usage
```text title="Usage"
tanomis session get ID
```
## Description
Show one session.
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/session/ls
# tanomis session ls
Reference for tanomis session ls: list sessions.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis session ls --help for the version you have installed.
## Usage
```text title="Usage"
tanomis session ls [flags]
```
## Description
List sessions.
Aliases: `list`.
## Flags
| Flag | Type | Description |
|---|---|---|
| `--agent` | string | Filter by agent name. |
| `--all-users` | bool | Include sessions from all users. |
| `--limit` | int | Maximum number of sessions. |
| `--status` | string | Filter by status. |
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/session/run
# tanomis session run
Reference for tanomis session run: invoke an agent: create a session, start a run, and stream its output.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis session run --help for the version you have installed.
## Usage
```text title="Usage"
tanomis session run AGENT [flags]
```
## Description
Invoke an agent from the CLI. Creates a session for AGENT in the caller's tenant, starts a run, and streams the run's events to stdout, the same runtime path the Console drives (no shortcut). Useful for development and for producing runtime evidence (tool / egress ledger entries). Bound by the caller's token and tenant scope; grants no privilege the Console would not.
## Flags
| Flag | Type | Description |
|---|---|---|
| `--contract-ref` | string | What this work is judged against, set when the work is opened and never afterwards. |
| `--contract-version` | string | The revision of the contract named by `--contract-ref` (required with it). |
| `--file` | stringSlice | Upload and attach a local file (repeatable). |
| `--idempotency-key` | string | Replay the original run on retry instead of starting a new one. |
| `--input` | string | Raw JSON input object (mutually exclusive with `--prompt`). |
| `--model` | string | Model for the new session (bounded by the agent's permitted_models when model.invoke enforces). |
| `--no-stream` | bool | Start the run and print its id without streaming events. |
| `--no-wait` | bool | When a rule holds the run for approval, print the hold and exit instead of waiting for the answer. |
| `--objective` | string | What this work is for, in your words, shown to whoever reviews it (set when the work is opened). |
| `--prompt` | string | Prompt string sent as \{"prompt": ...\}. |
| `--session` | string | Post the run to an existing session instead of creating one (needed to release a hold a person approved on it). |
| `--work-id` | string | Continue an existing unit of work instead of opening a new one (mutually exclusive with `--work-subject`). |
| `--work-subject` | string | Declare the subject of the work this run serves (settlement-mode agents refuse to run without work). |
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
## Examples
---
Source: /cli/session/stop
# tanomis session stop
Reference for tanomis session stop: stop a running session.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis session stop --help for the version you have installed.
## Usage
```text title="Usage"
tanomis session stop ID
```
## Description
Stop a running session.
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/skills
# tanomis skills
Reference for tanomis skills: install the platform skills for coding agents (Claude Code, Codex, Cursor).
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis skills --help for the version you have installed.
## Usage
```text title="Usage"
tanomis skills
```
## Description
Skills teach a coding agent how the platform works and how to drive this CLI: how to build an agent, deploy and roll it out, run and debug it, and govern it. They are embedded in this binary and rendered from its own command tree, so re-run install after upgrading.
## Commands
| Command | Description |
|---|---|
| [`tanomis skills install`](/cli/skills/install) | write the platform skills into a coding agent's skills directory |
| [`tanomis skills list`](/cli/skills/list) | list the platform skills embedded in this binary |
| [`tanomis skills show`](/cli/skills/show) | print one rendered skill without installing it |
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/skills/install
# tanomis skills install
Reference for tanomis skills install: write the platform skills into a coding agent's skills directory.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis skills install --help for the version you have installed.
## Usage
```text title="Usage"
tanomis skills install [flags]
```
## Description
Writes one SKILL.md per skill under the agent's skills directory: repo scope writes at the root of the current repository (.claude/skills, .agents/skills, .cursor/skills); user scope writes under your home directory. Re-running is safe: a file this CLI generated and nobody edited is updated in place; a file it did not generate, or one edited since install, is left alone unless `--force`. Do not edit installed skills: copy one under another name instead, so upgrades keep refreshing the original.
## Flags
| Flag | Type | Description |
|---|---|---|
| `--agent` | string | Coding agent(s) to install for: all, or a comma-separated list of claude, codex, cursor. Default `all`. |
| `--dir` | string | Explicit skills directory to write into (overrides `--agent` and `--scope`). |
| `--force` | bool | Overwrite skill files this CLI did not generate. |
| `--no-mcp` | bool | Do not register the documentation server (docs serve) with the agent. |
| `--scope` | string | Where to install: repo (the current repository) or user (your home directory). Default `repo`. |
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/skills/list
# tanomis skills list
Reference for tanomis skills list: list the platform skills embedded in this binary.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis skills list --help for the version you have installed.
## Usage
```text title="Usage"
tanomis skills list
```
## Description
List the platform skills embedded in this binary.
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/skills/show
# tanomis skills show
Reference for tanomis skills show: print one rendered skill without installing it.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis skills show --help for the version you have installed.
## Usage
```text title="Usage"
tanomis skills show SKILL
```
## Description
Print one rendered skill without installing it.
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/version
# tanomis version
Reference for tanomis version: print tanomis version information.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis version --help for the version you have installed.
## Usage
```text title="Usage"
tanomis version
```
## Description
Print tanomis version information.
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/workspace
# tanomis workspace
Reference for tanomis workspace: discover and select a managed-SaaS workspace.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis workspace --help for the version you have installed.
## Usage
```text title="Usage"
tanomis workspace
```
## Description
Discover and select a managed-SaaS workspace.
Aliases: `ws`.
## Commands
| Command | Description |
|---|---|
| [`tanomis workspace clear`](/cli/workspace/clear) | clear the active context's workspace selection |
| [`tanomis workspace ls`](/cli/workspace/ls) | list the organizations and tenants you can access |
| [`tanomis workspace show`](/cli/workspace/show) | show the workspace selected on the active context |
| [`tanomis workspace use`](/cli/workspace/use) | select a workspace and persist it on the active context |
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/workspace/clear
# tanomis workspace clear
Reference for tanomis workspace clear: clear the active context's workspace selection.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis workspace clear --help for the version you have installed.
## Usage
```text title="Usage"
tanomis workspace clear
```
## Description
Clear the active context's workspace selection.
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/workspace/ls
# tanomis workspace ls
Reference for tanomis workspace ls: list the organizations and tenants you can access.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis workspace ls --help for the version you have installed.
## Usage
```text title="Usage"
tanomis workspace ls
```
## Description
List the organizations and tenants you can access.
Aliases: `list`.
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/workspace/show
# tanomis workspace show
Reference for tanomis workspace show: show the workspace selected on the active context.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis workspace show --help for the version you have installed.
## Usage
```text title="Usage"
tanomis workspace show
```
## Description
Show the workspace selected on the active context.
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--tenant` | string | Tenant id (X-Tenant-ID). |
| `--verbose` | bool | Verbose output. |
---
Source: /cli/workspace/use
# tanomis workspace use
Reference for tanomis workspace use: select a workspace and persist it on the active context.
Generated from the tanomis command tree on 2026-10-01. Flags and defaults match the binary; run tanomis workspace use --help for the version you have installed.
## Usage
```text title="Usage"
tanomis workspace use ORG_SLUG [--tenant TENANT_ID] [flags]
```
## Description
Select one of your granted workspaces. The selection is validated against
GET /v1/me/workspaces (the only authority for what you may pick) and stored on
the active context. With `--tenant` omitted, the organization's default tenant is
used when there is exactly one obvious choice.
## Flags
| Flag | Type | Description |
|---|---|---|
| `--tenant` | string | Tenant id to select within the organization (default: the org's default tenant). |
Global flags
| Flag | Type | Description |
|---|---|---|
| `--config` | string | Config file path. |
| `--context` | string | Config context to use. |
| `-o`, `--output` | string | Output format: table\|json. |
| `--verbose` | bool | Verbose output. |
---
Source: /concepts/agents-and-versions
# Agents and versions
An agent is a name; a version is an immutable build plus its declared contract; enablement decides which tenants a version serves.
An agent on Tanomis is not a container you run. It is a name that owns a lineage of immutable
versions, and a set of per-tenant decisions about which version serves each tenant's traffic.
- **Agent**: a stable name, such as `support-copilot`.
- **Version**: one immutable build of that agent, with the manifest contract captured at
registration. Change anything and you publish a new version.
- **Enablement**: a tenant-scoped assignment that says "this tenant serves this version". No
version receives traffic until a tenant is enabled for it.
- **Warm start**: a version's sessions start from a pre-built snapshot when one exists, so a new
session is ready in well under a second instead of booting the harness cold.
## A version is immutable
`tanomis agent deploy` packages your source deterministically, builds it remotely, and registers the
result as a version with everything the manifest declared: entry point, harness, models, network
egress, policy ceiling, connectors, resources. That contract is validated at registration and
frozen for the version's lifetime.
This is what makes rollouts safe. A version that served traffic yesterday will behave the same way
today, and rolling back means enabling a version that already exists rather than rebuilding
anything.
## Version status and enablement are separate
A version has a **status**: it starts as a draft, and it must be at production status before a
tenant can serve it. Promotion changes the global status only. It routes no traffic on its own.
A tenant has an **enablement** for the agent: which version it serves as the baseline, and
optionally which version it serves as a canary at a percentage share. Enablement is the commit
point, and it is always tenant-scoped.
```bash title="Terminal"
tanomis agent versions support-copilot --by-tenant # who serves which version
tanomis agent rollout support-copilot --version v2 --tenants acme-prod --canary-share 10
```
`rollout` does both steps for the tenants you name: it promotes the version if needed, then
enables it for exactly those tenants, as their baseline or as a canary. Other tenants are never
touched. See [Roll out and roll back](/guides/deploy/rollout-and-rollback) for the full procedure.
`tanomis agent promote` moves a version to production status and nothing else. If you want a tenant to
serve it, enable it for that tenant, or use `rollout`, which does both.
## Warm starts
For a deployed version, Tanomis builds a golden snapshot: a checkpoint of the agent's warmed-up
process taken over an empty workspace, containing no tenant data. When a session starts, the
snapshot is restored and the session's own workspace is mounted into it, so the first turn does not
wait for the harness to boot.
You do not manage snapshots. The deploy builds or reuses one before the tenant is enabled, so the
first session never races the build. A version whose snapshot could not be produced still deploys;
its sessions start cold, and the deploy output says so.
Idle sessions are suspended the same way and restored when work arrives. An idle session costs
storage, not a running process, which is why sessions can be long-lived. See
[Sessions and runs](/concepts/sessions-and-runs).
## Models and the LLM gateway
A version declares the model it uses and, optionally, the models a run may request:
```yaml title="agent.yaml"
model: "your-workspace-model-id"
permitted_models:
- "your-workspace-model-id"
```
Every model call from an agent goes through the tenant's LLM gateway. That is where model access
is authorized, where usage is metered per run, and where model traffic is traced. An empty
`permitted_models` does not widen access: the decision falls to platform and tenant policy. A run
that requests a model outside the allowlist is refused.
## What a version carries
| Declared in `agent.yaml` | What it governs | Where it is enforced |
|---|---|---|
| `entrypoint`, `framework` | How the harness starts your code | Build and runtime |
| `model`, `permitted_models` | Which models a run may call | LLM gateway |
| `network.egress` | Which external hosts the agent may reach | Deploy (grammar and tenant ceiling), runtime proxy |
| `policy.approval_required_tools`, `policy.enable_ask_user` | Which actions pause for a person | Runtime, as durable interactions |
| `policy.scope_ceiling` | The most authority the agent's own tokens can carry | Delegation and token exchange |
| `integrations` | Which governed tools the agent may call | Gateway, per tool |
| `delegation` | Whether the agent calls or accepts work from other agents | Control plane, per call |
| `session.idle_timeout`, `session.max_lifetime` | How long a session lives | Session lifecycle |
The complete field list, defaults, and validation rules are in the
[Agent manifest reference](/reference/agent-manifest).
## Where to go next
- [Deploy a version](/guides/deploy/deploy-a-version): what happens between `tanomis agent deploy`
and an enabled tenant.
- [Roll out and roll back](/guides/deploy/rollout-and-rollback): canaries, graduation, and
rollback.
- [Policy, approvals, and evidence](/concepts/governance): the controls a version's contract
participates in.
---
Source: /concepts/governance
# Policy, approvals, and evidence
The controls that decide what an agent may do, the points where a person can step in, and the records that prove what happened.
Tanomis's promise is that an agent can act, and every action stays attached to who asked, what was
allowed, and what actually happened. This page is the map of those controls. Each one has its own
guide; this is where they fit together.
- **Declared ceilings** in `agent.yaml` bound what a version may reach: models, external hosts,
connector tools, and the authority its own tokens can carry.
- **Tenant ceilings** set by an administrator only ever narrow a declaration.
- **Approvals** pause a run for a person at the actions you choose, and survive disconnects.
- **Two evidence planes** record it: a tamper-evident audit ledger for decisions and attribution,
and an execution trace for what the agent did, kept apart on purpose.
## Every control is a ceiling, and ceilings only narrow
A version declares what it needs. An administrator may set a per-tenant ceiling. The effective
permission is the intersection, and no layer can add to what a lower one declared.
| Control | Declared by the author | Bounded by the tenant | Enforced at |
|---|---|---|---|
| Models | `model`, `permitted_models` | Tenant model policy | The LLM gateway, per call |
| External hosts | `network.egress` | Tenant egress ceiling | Deploy time, then the runtime proxy per connection |
| Connector tools | `integrations..tools.allow` | Compatible connection and agent access | The gateway, per tool call |
| Agent authority | `policy.scope_ceiling` | The caller's own ceiling on a delegated call | Delegation and token exchange |
Where a declaration exceeds the tenant ceiling, the deploy fails and names both the entry and the
ceiling that excluded it, before anything is built. You find out in seconds.
## Approvals: a person decides at the moments you choose
Two manifest settings turn specific moments into durable interactions:
- **`policy.approval_required_tools`** lists the harness tools that must wait for a permission
decision. Each call emits a permission request; the run pauses until a person allows or denies.
- **`policy.enable_ask_user`** lets the agent ask a person a question mid-run and wait for the
answer.
Governed connector tools can be marked the same way with `approval_required_tools` on the
integration grant.
An interaction is a control-plane record, not a live socket. It survives a closed browser tab, a
restarted application, or a suspended session. A decision is accepted exactly once, so two
reviewers cannot both answer; the second sees the recorded answer. The application flow is in
[Approval-required actions](/guides/run/approval-required-actions); the Console renders the same
requests as allow/deny cards.
## Identity: who asked, and who acted
Every request carries two identities kept apart: the **invoking principal** (the person or
service that started the work, resolved from the access token and the selected workspace) and the
**agent actor** (the version that acted, attested by the platform with a per-run identity that
the agent process cannot forge).
When one agent hands work to another, the callee's effective authority is what it was granted for
that call, intersected with both agents' ceilings. Authority narrows at every hop and never widens.
See [Identity and access](/concepts/identity-and-access) and
[Delegate work to another agent](/guides/run/delegate-to-another-agent).
## Evidence: two records, kept apart
| Plane | What it records | Properties | Who uses it |
|---|---|---|---|
| Audit ledger | Attribution, policy decisions, approvals, delegation decisions, enablement changes, content hashes | Tenant-isolated, append-only, hash-chained, signed. It carries references and decisions, never prompt or tool payload bodies. | Auditors, compliance, incident review |
| Execution trace | One trace per run: model calls, tool calls, timing, and content captured at the level tenant policy allows | Mutable evaluation data with its own capture, sampling, and retention policy | Engineers debugging behaviour, evaluators |
They share the run and session identifiers, so a ledger decision can point at the trace content it
was made about, by hash, without copying the content into the ledger. Read the organization's
trail with `tanomis org audit`; see [Audit and evidence](/guides/audit/audit-and-evidence).
A valid ledger chain proves the platform's record is intact and unaltered. It does not by itself
prove that an external system changed the way the agent intended. For effects on other systems,
verify in that system.
## What every run gets
The causal order is **identity → context → policy checkpoint → agent action → optional human
decision → evidence**.
- **Identity on both sides.** The person or service that asked, and the exact agent version that
acted, attested by the platform.
- **A policy checkpoint before the action.** Models, hosts, connector tools, and delegated
authority are ceilings that only narrow.
- **A person when you want one.** Approval requests and questions pause the run and survive
disconnects; the first answer wins.
- **Evidence you can hand to an auditor.** A signed, append-only ledger of decisions, and a
separate execution trace of what the agent did.
> **Availability:** Shared connectors are configured per tenant; durable approvals are on in hosted Tanomis. See [What is available where](/reference/availability).
## The journey, with the controls in place
1. **Build**: declare models, hosts, connectors, approvals, and the authority ceiling in
`agent.yaml`.
2. **Deploy**: the declaration is validated against the tenant's ceilings before a build starts,
then frozen into the version.
3. **Run**: the gateway, the egress proxy, and the connector gateway enforce per call; approvals
pause the run where you asked them to.
4. **Review**: the ledger holds the decisions; the trace holds the execution.
## Where to go next
- [Restrict network egress](/guides/govern/restrict-network-egress)
- [Approval-required actions](/guides/run/approval-required-actions)
- [MCP connectors](/guides/connect-tools/mcp-connectors)
- [Audit and evidence](/guides/audit/audit-and-evidence)
---
Source: /concepts/identity-and-access
# Identity and access
Who asked, who acted, and how a request is resolved. The identity postures an agent can declare, what the platform actually establishes today, and why a declaration never grants access.
Identity answers **who is acting**. Access answers **whether that actor may perform this action
now**. Tanomis keeps the two apart: an identity mode declares the posture an agent intends, the
authorization path narrows what any credential may carry, and the target resource makes the final
decision.
- Two identities on every request: the **invoking principal** (person or service, from the token) and the **agent actor** (the version, attested by the platform).
- An agent's **identity mode** declares its posture; its **scope ceiling** is the most authority its own tokens can ever carry.
- Authority only narrows: brokers intersect, they never widen. Delegated authority is what was granted for the call, not the agent's registered envelope.
- The evidence records both identities, so an auditor can see who asked and which version acted.
> **Availability:** Service-mode bind-time identity is enabled per tenant by a platform operator; where it is off, the session runs without an identity assertion. Scoped and delegated authority are held contracts: a public runtime workflow for scoped service API authority is not currently exposed. See [What is available where](/reference/availability).
Service (conditional): the stable agent machine principal can receive a per-bind identity assertion
with no resource or scopes; it is not run-bound, and scoped service API authority has no supported
public runtime path. Delegated
(held): the contract intends a user subject plus agent actor and an on-behalf-of credential, but no
generally supported tenant-bound, agent-attributed production exchange exists yet. Hybrid
(conditional): bind-time identity is conditional, while both scoped authority paths are held; one credential
path is intended per acquisition and authority is never combined. If a future supported flow
requests scoped authority, the contract model applies an effective ceiling and any supplied target
audience. Approval classification is delegated-only, has no supported completion path, and
autonomous acquisition skips it. The authorization contract can remain cached for up to 30 seconds.
Declaring an identity mode, scope, audience, model, or tool is never a grant. Each declaration is a
ceiling, posture, or request that a later enforced decision can narrow or refuse.
## How a request is resolved
Four things turn a request into an authorized action, in this order: **CLI context →
authenticated principal → selected workspace projection → organization + tenant**.
1. A **CLI context** is local configuration: a named target environment plus its authentication
state. It is not an organization or tenant.
2. The credentials identify the **authenticated principal**: the user, service, or agent whose
authority bounds the request.
3. Workspace discovery returns only the projections granted to that principal. A **selected
workspace** combines one organization, one attached tenant, the caller's role, and effective
capabilities.
4. The **organization and tenant** in that projection scope the resources the request may touch.
Selecting a workspace supplies request context; it does not create a resource or widen the
caller's grants. The API's `X-Organization-ID` and `X-Workspace-Tenant-ID` headers select one
granted projection; despite its compatibility name, `X-Workspace-Tenant-ID` carries the selected
tenant ID. See [Platform API request basics](/api#request-basics).
## Two identities on every run
The **invoking principal** starts the request: a signed-in person, a backend with an
[automation client](/guides/deploy/automation-credentials), or another agent. The selected
workspace supplies the organization and tenant that principal may use.
The **agent actor** is the version that does the work. Where service-mode identity is enabled, the
session bind also establishes a stable machine identity for the agent process. The session records
the immutable execution version regardless.
| Question | Answer today |
|---|---|
| Who selected the organization and tenant? | The invoking principal and its selected workspace. |
| Which code is running? | The immutable version and tenant enablement bound to the session. |
| Which identity can be established at bind? | The stable agent machine principal. Its assertion has no resource or scopes and is not proof of one run. |
| Which ceiling would a scoped request use? | The agent's broker-cached authorization contract, not the session's execution version. No public runtime flow makes that request today. |
| Was the final action allowed? | The target resource's own authorization result, never the manifest or token alone. |
Delegated and hybrid modes describe an intended model in which a user remains the subject and the
agent is verifiably attributed as the actor. Do not describe a delegated token or audit entry as a
verified on-behalf-of chain until a supported flow says it is.
## Identity modes
Hosted manifests must declare one mode:
```yaml
identity_mode: service
inherits_user_permissions: false
```
| Mode | Intended authority | Availability |
|---|---|---|
| `service` | The agent's own machine principal. | Bind-time identity is enabled per tenant and can degrade to a session without an assertion. The runtime requests no scoped API authority. |
| `delegated` | A user subject with the agent verifiably attributed as actor. | Contract-defined; the tenant-bound, agent-attributed exchange is held. Autonomous acquisition is rejected. |
| `hybrid` | Service or delegated authority, one credential path per acquisition. | Bind-time identity is conditional; both scoped authority paths are held. The two authorities are never unioned. |
`identity_mode` is pinned by the agent's first non-rejected version and must stay the same across
later versions; a rejected first attempt does not pin it. A different trust model is a different
agent. `inherits_user_permissions: true` is valid only with `delegated` or `hybrid`, and opts the
version into an impersonation approval gate before it can become serveable; passing that gate does
not make the held delegated flow available.
## What the service assertion is, and is not
When enabled and established at session bind, service-mode identity asks for no target resource
and no scopes. The result is an **identity assertion**, not a scoped API credential. The runtime
injects it into the agent process and records the returned stable fingerprint on session-start
evidence; a warm process can reuse the assertion until it expires, so it does not prove which run
presented it. A record without the fingerprint means no identity was established for it;
historical records are not backfilled.
The manifest still declares `policy.scope_ceiling` and `policy.allowed_audiences` as the agent's
authorization contract. A future supported flow would fit requested scopes to that ceiling and
check a target audience. Today, the shipped runtime request supplies neither, no public workflow
accepts a scoped service-token request, and autonomous acquisition does not evaluate approval
sensitivity rules. The intended delegated design adds that effective scopes also fit the user's
entitlement, one acquisition uses one user-backed credential path, and the credential and evidence
preserve the user subject and verified agent actor. The hosted exchange currently
defers user-entitlement intersection to identity-provider issuance, so requested scopes are not
proof of effective scopes; the complete path is held, and its approval-required
response has no durable approval ID, decision endpoint, or resume path. Durable runtime
[questions and tool decisions](/guides/run/approval-required-actions) are a separate, supported
mechanism.
Do not infer scoped API authority from a service identity token, call an internal token endpoint,
or build an approval UI from these concepts. Keep final action authorization at the target resource.
## Changing a version's authorization
A version's manifest is immutable; to change `scope_ceiling`, `allowed_audiences`, or approval
thresholds, register a new version. The scoped-authorization contract has its own clock: where the
internal broker exercises it, it reads the agent's effective registered manifest through a
30-second in-process cache, not the session's execution version. A registration can change that
contract before the new version receives traffic, and rolling execution traffic back does not
necessarily restore the earlier contract. Review authorization impact at registration time and
verify the effective contract after a change; use
[Roll out and roll back](/guides/deploy/rollout-and-rollback) to move traffic, not as an
authorization rollback.
## Three controls, three questions
| Control | What it bounds |
|---|---|
| `policy.scope_ceiling` | Concrete authority a minted agent token may carry. |
| `permitted_models` | Which model a run may select. |
| Integration `tools.allow` | Which published-contract tools the agent requests; current workspace access is checked separately. |
Adding an `mcp:...` scope does not approve a connector tool, and adding an `llm:...` scope does not
replace the model allowlist. See the [connector authority gates](/guides/connect-tools/mcp-connectors#read-the-four-independent-gates).
## When it stops
| When | Typical reason | What to do |
|---|---|---|
| Validate or deploy | Unknown mode, wildcard grant, missing audience for a non-empty ceiling, or `inherits_user_permissions` with service mode. | Correct the manifest before publishing. |
| Register a later version | Its `identity_mode` differs from the pinned mode. | Keep the mode, or create a separate agent. |
| Establish bind-time identity | Identity is disabled, fails softly, or is denied in enforce mode. | An absent fingerprint is the visible no-identity result; confirm tenant configuration with your platform operator. |
| Authorize the final action | The target resource rejects the token or operation. | Inspect that resource's policy outcome. A declaration cannot override it. |
| Design scoped or delegated API authority | You need a public scoped token flow, tenant-bound delegation, or approval completion. | The public path is held. Wait for a supported integration guide. |
Treat an infrastructure error separately from a policy denial, and never widen the manifest to get
past a transient failure.
## Reading attribution
The Console's agent identity view shows the stable non-secret fingerprint, pinned mode,
provisioning state, ceiling, and allowed audiences. It never shows the identity provider's
principal reference or a client secret. Session and ledger evidence identify the agent and version
and include the fingerprint when an identity was established. Session views describe the recorded
subject plus the configured posture; they do not prove a fully persisted delegation chain.
The `tanomis session run` command intentionally prints session and run identifiers and agent output,
not machine-identity fields.
Never copy a provider client ID, secret, or internal token into application logs.
## Backend and CI identity
An [automation client](/guides/deploy/automation-credentials) identifies the backend or pipeline
making a platform request. It is separate from the identity an agent uses while running. Owners
and admins choose its permissions; the platform checks its live state and tenant membership on
every decision, so disabling the client or removing a scope applies without waiting for a token to
expire.
## Where to go next
- [Agent manifest](/reference/agent-manifest#attribution-and-identity): the identity and policy
fields.
- [Sessions and runs](/concepts/sessions-and-runs): the durable session and each admitted run.
- [Policy, approvals, and evidence](/concepts/governance): where identity sits among the controls.
---
Source: /concepts/organizations-and-tenants
# Organizations and tenants
How a customer organization, its members and roles, its runtime tenants, and the workspace you select fit together.
Your organization is your account. Your tenants are where agents run. A workspace is one
organization plus one of its tenants, as seen by you. Everything you do with `tanomis`, the Console, or
the Platform API happens inside one workspace.
- An **organization** owns members, roles, invitations, and one or more tenants.
- A **tenant** is the runtime and data-isolation boundary: agents, versions, sessions, and files
live in exactly one tenant and never cross it.
- A **workspace** is your view of one organization and one attached tenant, with the role and
capabilities you hold there. You select it once; the server re-checks it on every request.
## The three objects
| Object | What it holds | What it is not |
|---|---|---|
| Organization | Members, roles, invitations, tenant attachments, the audit trail | A runtime boundary. Nothing executes "in" an organization. |
| Tenant | Enabled agents and versions, sessions, runs, files, connectors, policy ceilings | A customer account. A tenant belongs to exactly one organization. |
| Workspace | The pair you selected plus your effective role and capabilities | A third resource. It is a projection of the other two for one caller. |
An organization owns one tenant per customer, and usually a sandbox tenant that serves no
customer. Adding a tenant later does not change identities, credentials, or API contracts. One
attachment can be marked as the organization's default.
## Roles and capabilities
Every member has one role in the organization. Roles map to a small, fixed set of capabilities.
| Role | Reach | Capabilities |
|---|---|---|
| Owner | Every tenant in the organization | `manage`, `deploy`, `run`, `view` |
| Admin | Every tenant in the organization | `manage`, `deploy`, `run`, `view` |
| Developer | Only tenants named in an explicit grant | As granted, from `deploy`, `run`, `view` |
| Viewer | Only tenants named in an explicit grant | As granted, usually `view` |
What the capabilities mean in practice:
- **`view`** inspects agents, versions, sessions, runs, and files in the tenant.
- **`run`** creates sessions and runs enabled agents.
- **`deploy`** deploys and changes agent versions and their enablement.
- **`manage`** governs connector configuration where that is separately enabled. It does not grant
organization or tenant administration; membership and grants are owner and admin work.
A grant names a member and a tenant. Accepting an invitation automatically grants the
organization's default tenant; if the organization has no default, the new member needs a grant
before they can select a workspace.
## How access is decided
Access is never something a client asserts. On every request the server resolves:
1. **Who you are** from the access token.
2. **Which organization** you asked for, and that you are a member of it.
3. **Which tenant** you asked for, that it is attached to that organization, and that your role
or a grant reaches it.
4. **Whether the operation** is within your capabilities there.
Naming an organization or tenant in a header or a CLI flag can only narrow what your token
already allows. It cannot widen it, and it cannot reach a tenant you are not granted. Memberships
and grants can change at any time; the server re-resolves them when each request arrives, so a
revoked grant takes effect on the next call.
## Selecting a workspace
Discovery returns only what you may select:
```bash title="Terminal"
tanomis workspace ls
tanomis workspace use northwind --tenant bluewater-bank
tanomis workspace show
```
`workspace ls` lists your organizations and the tenants each grants you, with each tenant's state.
Select a tenant whose state is `ready`; a tenant that is still being provisioned can be saved as a
selection, but the server refuses tenant-scoped operations until it becomes ready.
Applications do the same discovery over HTTP with
[List workspaces](/api/workspaces/list-workspaces), then send the organization and tenant as
request headers. See [Invoke from your application](/guides/run/invoke-from-your-application).
## Organization automation
An organization also owns its [automation clients](/guides/deploy/automation-credentials).
Each client reaches all active, ready tenants attached to that organization, including future
tenants. Its permission profile restricts actions across that reach. Human tenant grants and
machine permissions are separate: machines do not inherit a human workspace selection or use
per-tenant grants. A signed-in owner or admin manages their lifecycle.
## Where to go next
- Invite people and grant tenants: [Organization members and access](/guides/manage/organization-members).
- Understand how agents and versions live inside a tenant: [Agents and versions](/concepts/agents-and-versions).
- Sign in and select a workspace from the CLI: [Authenticate and select a workspace](/getting-started/authenticate-and-select-workspace).
---
Source: /concepts/platform-model
# Platform model
The six nouns that explain Tanomis: platform, organization, tenant, agent version, session, run. How they nest, who works at each level, and what an operation changes.
Six nouns explain everything in Tanomis: **platform**, **organization**, **tenant**, **agent
version**, **session**, **run**. You are the organization; your customers are the tenants. Here is
how they nest, who works at each level, and what an operation changes.
## How things nest
Your company is one **organization**. Each of your customers is a **tenant**: their own isolated
runtime, with its own policy, connectors, sessions, and data, served by the agents you enable for
them. An agent you deploy once can serve every customer, each on the version you choose. The
**platform** underneath is Tanomis's in the hosted service and yours in a self-hosted install;
either way, it hosts your organization and runs the shared infrastructure.
Need somewhere to test? Keep an internal tenant that serves no customer; a canary to one customer
works the same way. Inside every tenant the runtime chain is the same:
**agent → version → enablement → session → run**. An agent is a stable name. A version is an
immutable build of it. An enablement says which version this tenant serves. A session is a
conversation or unit of work against that version, and a run is one request inside the session.
Files, approvals, and questions attach to the session; the audit ledger and trace record every run.
## Who works where
| Role | Level | What they do | Tool |
|---|---|---|---|
| Platform operator | Platform | Installs and upgrades Tanomis, provisions tenants, deploys global agents, sets tenant ceilings for models, egress, and connectors. Tanomis staff in the hosted service; your own team when self-hosted. | `tanomis`, Console |
| Organization owner or admin | Organization | Invites members, sets roles, grants tenants, creates automation credentials, reads the audit trail. | Console, `tanomis` |
| Developer | Tenant | Writes agents, deploys versions, canaries a new version to a few customers and rolls back, evaluates. | `tanomis`, CI |
| Your application | Tenant | Creates sessions and starts runs on behalf of a customer, in that customer's tenant; streams events, answers approvals, attaches files. | Platform API |
| Reviewer or auditor | Tenant | Answers approvals in the Work queue, reads runs, exports evidence packages. | Console |
## The six nouns
| Noun | What it is | Owned by | Changes when |
|---|---|---|---|
| Platform | One Tanomis deployment: shared infrastructure, global agents, and the operators who run it. Hosted by Tanomis, or installed by you. | Platform operators | You install or upgrade it. |
| Organization | Your company on Tanomis: its members, roles, tenant grants, and automation credentials. One per customer. Nothing runs here. | Your owners and admins | A member, role, or grant changes. |
| Tenant | One of your customers: an isolated runtime with the agents it serves, its policy ceilings, its connectors, and its data. Nothing is shared between tenants. | Your organization | Policy or enablement changes; offboarding destroys it. |
| Agent version | An immutable build of a named agent plus its declared contract. An **enablement** says which version a tenant serves. | Whoever deployed it | Never. A change is a new version; enablement moves traffic. |
| Session | A conversation or unit of work against one version in one tenant, with its workspace, files, and approvals. Suspended when idle. | Your application, or the person who opened it | Each run adds to it; it expires by idle or lifetime limit. |
| Run | One request inside a session, streamed as events, ending in one authoritative status. Every run leaves a ledger entry and a trace. | The session | Only its status, until it is terminal. |
## What changes what
| You do | It creates or changes | Scope |
|---|---|---|
| `tanomis agent deploy` | A new immutable version, its warm-start snapshot, and an enablement. | The tenants named, or all with `--scope global --tenants all` |
| `tanomis agent promote` | The version's global status only. No traffic moves. | Platform |
| `tanomis agent enable` or `rollout` | Which version a tenant serves, as baseline or canary share. | The tenants named |
| Change tenant policy | Ceilings for models, hosts, connectors, spend, for one customer. Applies to their next run. | One tenant |
| Grant or revoke a member | Who may see or act in a tenant. Takes effect on the next request. | Organization |
| Offboard a tenant | When a customer leaves: destroys their sessions, workspaces, and enablements. Ledger exports survive. | One tenant, confirmed by name |
## Where to go next
- [Organizations and tenants](/concepts/organizations-and-tenants): roles, grants, and how
access is decided.
- [Agents and versions](/concepts/agents-and-versions): immutability, enablement, warm starts.
- [Sessions and runs](/concepts/sessions-and-runs): the execution lifecycle.
- [Identity and access](/concepts/identity-and-access): who asked, who acted, and how the API
resolves a request.
---
Source: /concepts/security-model
# Security and isolation model
How agents are isolated from each other and from the platform, how tenants are kept apart, and what is signed.
An agent runs untrusted code on behalf of your tenant. Tanomis's job is to let it act while
keeping it inside a boundary: isolated from other agents, unable to reach anything you did not
declare, and unable to forge its own identity or its own record.
- Each session runs in its own sandbox with a kernel-level isolation layer, on workers that are
interchangeable and hold no session identity.
- Tenants never share a gateway, a workspace, or a snapshot's data.
- Everything an agent reaches goes through a platform-controlled path: the LLM gateway, the
connector gateway, the egress proxy, the delegation broker.
- Images and golden snapshots are signed; run identity is a platform-signed assertion custodied
outside the agent process.
## Isolation
| Boundary | How it is kept |
|---|---|
| Between sessions | A nested sandbox per session with a kernel-level isolation layer. The worker pod that hosts it is generic: session identity lives in the control plane, the workspace, and the snapshot, never in the worker. |
| Between tenants | Per-tenant worker pools, per-tenant LLM gateway, per-tenant trace and ledger partitions, tenant-scoped storage. A request that names another tenant is refused at the front door. |
| Between an agent and the platform | The agent sees `/workspace` and the platform's own endpoints. Every other destination is refused unless declared and inside the tenant ceiling. |
A golden snapshot, the warm-process checkpoint sessions start from, is taken over an empty
workspace and contains no tenant data. The session's own workspace is mounted into it at restore.
## Every path out is governed
| Path | Control |
|---|---|
| Model calls | The tenant's LLM gateway: allowlist, metering, tracing |
| Tool calls | The connector gateway: manifest-declared connector and tools, current agent access, connection and account readiness, exact-call approvals |
| External hosts | A per-connection egress proxy: declared hosts within the tenant ceiling, public addresses only |
| Other agents | The delegation broker: a live grant, narrowed authority, a recorded decision |
There is no direct network route from an agent to the internet or to the cluster it runs in.
## Identity that cannot be forged
The invoking principal comes from the access token; the agent actor is attested by the platform
with a per-run assertion custodied outside the agent process. Anything the agent process can read
authenticates nothing on its own. See [Identity and access](/concepts/identity-and-access).
## Supply chain
Agent images are built remotely from a deterministic bundle and registered by digest. Golden
snapshots are signed platform artifacts. A version's contract is captured at registration and
immutable after it. What ran is therefore what was reviewed.
## Records that cannot be quietly edited
The audit ledger is append-only, hash-chained, and signed per tenant; the execution trace is kept
apart under its own capture and retention policy. See
[Audit and evidence](/guides/audit/audit-and-evidence).
## Where to go next
- [Policy, approvals, and evidence](/concepts/governance)
- [Restrict network egress](/guides/govern/restrict-network-egress)
- [Data handling and retention](/guides/govern/data-handling): planned page on storage and
retention.
---
Source: /concepts/sessions-and-runs
# Sessions and runs
A session is where state lives; a run is one request inside it. Statuses, one-active-run, idempotent retries, and what to do when a run waits for a person.
A **session** is the continuity boundary for one enabled agent version: its workspace, files, and
interactions. A **run** is one admitted unit of execution inside it. Keep both IDs: the session
tells you where state lives; the run tells you what happened during one request.
- Create a **session** once per conversation or unit of work; start a **run** per request. Runs in a session execute one at a time.
- Send an `idempotency_key` on both so a retry returns the original instead of duplicating work.
- The **run resource** is the authoritative status. The event stream is how you watch it; reconnect with `Last-Event-ID`.
- A run can pause for a person (`awaiting_input: true`); a session can be stopped and resumed, and idle sessions are suspended for you.
Session `ready` → Run 1 `running` → interaction `pending` (`awaiting_input: true`) → Run 1
`succeeded` → session `idle` → Run 2 `running` → Run 2 `failed` → session `idle`.
Run 2 starts only after Run 1 is terminal; the failed second run does not erase the first or end
the session.
## Choose the invocation boundary
| Choose | When it fits | Tradeoff |
|---|---|---|
| [One-shot invocation](/api/agents/invoke-agent) | One independent request where immediate streaming is the main goal. | Tanomis creates a session and run and returns their IDs in `X-Session-ID` and `X-Run-ID`, but retrying the request creates a new session boundary. |
| [Explicit session](/api/sessions/create-session) | Retries, multiple runs, durable interaction, files, or reconnecting to events matter. | Two requests to begin: create the session, then start the run. |
One-shot still creates durable resources; record the IDs it returns. For anything your product
depends on, create the session explicitly.
## Read resource state
Session status describes whether the continuity boundary can accept or continue work:
| Session status | Meaning |
|---|---|
| `creating` | Tanomis is provisioning the session. |
| `ready` | The session is prepared and can accept a run. |
| `running` | A run is active. |
| `idle` | No run is active; the session can be reused. |
| `stopping` | Stop is fencing new work and releasing the active runtime. |
| `stopped` | The session was explicitly stopped and must be resumed before another run. |
| `failed` | Session activation or runtime failed; explicit resume can attempt recovery. |
| `expired` | The session reached its lifecycle limit; explicit resume can reactivate it. |
Run status describes one execution attempt:
| Run status | Meaning |
|---|---|
| `queued` | Admitted and waiting for dispatch. |
| `running` | Executing or waiting on a durable human interaction. |
| `succeeded` | Completed successfully. |
| `failed` | Ended with an error; inspect `error_code` and `error_message` when present. |
| `cancelled` | Ended after cancellation. |
`succeeded`, `failed`, and `cancelled` are terminal. A terminal run stays readable through the
[run resource](/api/runs/start-run), and the session returns to `idle` for the next run.
The public session status does not reveal worker residency. An `idle` session may keep a runtime
warm or be transparently suspended and restored when work arrives. Do not call resume for that;
reserve it for `stopped`, `failed`, or `expired` sessions.
## One run at a time
One session admits at most one active run, where active means `queued` or `running`. Starting
another run while one is active returns `409 Conflict`. Wait for a terminal state, use another
session for independent concurrency, or retry the same run idempotently. Terminal runs do not hold
the slot, so a reused session is an ordered history:
```text
session A
run 1: succeeded
run 2: failed
run 3: running
```
Files and durable interactions belong to the session. Attachments select session files for one
run; they do not move the files anywhere.
## Make retries deliberate
Use a stable idempotency key for one logical operation and never recycle it for new intent. Keys are
opaque, at most 255 bytes, and scoped differently at each boundary:
| Operation | Same key | Changed intent or no key |
|---|---|---|
| Create session | For the same authenticated caller, tenant, and provisioning inputs, returns the original non-terminal session with `200 OK`. | Changed provisioning inputs return `409 Conflict`. Reusing the key after the original session is `stopped`, `failed`, or `expired` returns `412 Precondition Failed`. Omitting the key creates a new session. |
| Start run | Within the same session, returns the original active or terminal run; a JSON replay is `200 OK`, while a streaming replay follows the original event stream. | Changed input, model, or work does not replace the original run, so bind the key to one immutable request in your client. Attachment-set drift returns `409 Conflict`. A new or absent key starts a new run only when no run is active; otherwise it returns `409 Conflict`. |
The run key is scoped to the session; the same string in another session refers to nothing.
A one-shot retry creates another session first, so a run key in its body cannot make the whole
one-shot operation retry-safe.
## Inspect, cancel, stop, and resume
Resource reads are authoritative. A stream close, command exit, or accepted cancellation does not
replace them.
| Action | Request | Then |
|---|---|---|
| Read | `GET /v1/sessions/{session_id}` and `GET /v1/sessions/{session_id}/runs/{run_id}` | Use the run's status; the session read carries `awaiting_input`. |
| Cancel | `POST /v1/sessions/{session_id}/runs/{run_id}/cancel` | Read the run until it is terminal. The session's history is untouched. |
| Stop | `POST /v1/sessions/{session_id}/stop` | Stop fences new runs and settles active work before `stopped`. A conflict during admission means the boundary is still settling: inspect and retry. |
| Resume | `POST /v1/sessions/{session_id}/resume` | Resume only a `stopped`, `failed`, or `expired` session. It keeps the session ID and pinned version and returns to `ready`; on a live session it conflicts. |
## Handle a run waiting for a person
> **Availability:** Durable interactions are on in hosted Tanomis and operator-enabled when self-hosted. See [What is available where](/reference/availability).
A pending interaction adds no run status. The run remains `running`; a session read exposes the
derived signal `awaiting_input: true`. List the session's pending interactions, answer by the
stable `interaction_key`, and keep observing the same run. Tanomis may suspend and restore the
runtime while it waits without changing the public model. The full first-answer-wins flow is in
[Approval-required actions](/guides/run/approval-required-actions).
A run may instead bind to durable settlement work that can continue
from a later session; see [Durable work and settlement](/guides/run/durable-work-and-settlement).
## Keep lineage separate
A later run continues the same session. It is not a fork and it is not a delegated session; those
create separate lineage and authority and have their own guides.
## Where to go next
- [Invoke from your application](/guides/run/invoke-from-your-application): the six-step flow
from a backend.
- [Stream and replay events](/guides/run/stream-and-replay-events): watching a run safely.
- [Session workspace and state](/guides/build/session-workspace-and-state): what persists.
---
Source: /getting-started/authenticate-and-select-workspace
# Authenticate and select a workspace
Sign in to hosted Tanomis as a person, choose a CLI context and granted workspace, and keep that human login out of application backends.
Sign in as yourself, pick one workspace you are granted, and check the result before you deploy or
run anything. Most people do this once per machine.
- `tanomis login` is a browser device flow for a person; the CLI stores the login in a named
**context**.
- A **workspace** is one organization and one tenant you are granted. `tanomis workspace use` selects
it; `tanomis workspace show` proves it.
- The server re-checks membership and grants on every request. Selecting never widens access.
- Pipelines and backends use an automation client, never this login.
> **Backend boundary:** This login belongs to the person using the CLI. Do not read the CLI
> credential store from application code, copy its refreshable credentials into a service, or use
> platform-service or agent machine credentials. Pipelines and backends use an organization-owned
> automation client; see
> [Automation credentials](/guides/deploy/automation-credentials).
The complete path is:
```text
Hosted API -> named CLI context -> signed-in person -> granted workspace -> tenant-scoped request
```
The first three choices are local request context. The server still verifies the credential,
organization membership, tenant grant, and required operation on every request.
## 1. Sign in to a named context
```bash
tanomis login --console https://api.tanomis.com --name saas
```
`tanomis` reads the public environment configuration from the hosted API, so it discovers the API
origin and OIDC settings rather than relying on copied values. Device authorization is the hosted
default: the CLI prints a verification address and a short code, you sign in and approve in a
browser as yourself, and the CLI stores short-lived access and refreshable login state for the
`saas` context. A device flow can start on a headless terminal, but it is not unattended: a person
must finish the browser step. Do not automate it as a service credential.
```bash
tanomis auth status
tanomis config current-context
```
`auth status` shows the context, authentication mode, API target, login state, and token expiry,
never the token itself. A normal result names `saas` and reports `logged-in: yes`. Tokens are
short-lived; `tanomis` refreshes them before a command, and if the refresh grant is gone it stops and
asks you to `tanomis login` again.
## 2. Understand context versus workspace
A context chooses an environment and its local authentication configuration. A workspace chooses
one organization and attached tenant from the projections granted to the signed-in person.
| Object | What it selects | What it cannot do |
|---|---|---|
| CLI context | One API environment and its login state | Grant organization or tenant access |
| Authenticated principal | The person whose authority bounds the request | Borrow another member's role |
| Selected workspace | One granted organization, tenant, role, and capability set | Create, attach, or reassign a tenant |
| Tenant | The runtime and data-isolation boundary | Expand the caller's credential or membership |
Use another configured context as the default, or target it for one command:
```bash
tanomis config use-context staging
tanomis config current-context
tanomis workspace ls --context staging
```
Login and workspace selection are stored per context, so switching context can change the API
environment, the cached login, and the selected workspace together. Check both before a
consequential command. The current CLI does not display the account identity behind a cached
login; to establish it again, log out and log in as the intended account.
## 3. Discover your granted workspaces
```bash
tanomis config use-context saas
tanomis workspace ls
```
```text
ORGANIZATION DISPLAY NAME ROLE TENANT STATE DEFAULT CAPABILITIES
northwind Northwind developer bluewater-bank ready * run,view
northwind Northwind developer northwind-sandbox ready deploy,run,view
```
The list contains only organizations you belong to and only tenants granted to you. Capabilities
summarize what you may do in each:
| Capability | What it allows |
|---|---|
| `view` | Inspect permitted resources |
| `run` | Create sessions and run enabled agents |
| `deploy` | Deploy or change agent versions where the command permits it |
| `manage` | Govern supported connector configuration when separately enabled; it does not grant organization or tenant administration |
Select a tenant whose `STATE` is `ready`. Discovery can include an attachment that is still being
provisioned, and the CLI can save that selection, but the server refuses tenant-scoped operations
until the attachment becomes ready.
The table is discovery, not a permanent authorization snapshot. Memberships and grants can change,
and the server re-resolves them when each request arrives. `workspace use` also saves the displayed
capabilities as a local advisory snapshot, so deploy and run commands may deny early when it lacks
their capability. After an owner changes a grant, repeat `workspace use` and `workspace show`.
## 4. Select and verify one projection
```bash
tanomis workspace use northwind --tenant northwind-sandbox
tanomis workspace show
```
```text
context: saas
organization: northwind
tenant: northwind-sandbox
role: developer
capabilities: deploy, run, view
```
If the organization has one tenant, or exactly one default, you may omit `--tenant`. With several
tenants and no single default, the CLI refuses to guess and asks for `--tenant`. It also rejects an
organization or tenant that was not returned by discovery.
The selected tenant becomes the command's effective tenant, so omit a redundant `--tenant` flag.
Deploy and session-run commands refuse an explicit tenant that differs from the selected workspace;
do not assume every other command has that local conflict check.
## 5. Use the workspace, then clean up deliberately
```bash
tanomis agent ls
```
For a first deploy and managed run, follow the [Quickstart](/getting-started/quickstart). Its
commands name a tenant explicitly; when a workspace is selected, an explicit `--tenant` must
exactly match the tenant from `workspace show`, and you can simply omit it.
```bash
tanomis workspace clear
tanomis logout
```
`workspace clear` removes the local organization and tenant choice from the context and nothing on
the server. `logout` clears the cached login and keeps the context; authenticated commands stop
until you log in again. Deleting a context is a separate action that also clears its credentials.
## Troubleshoot authentication and selection
| What you observe | Meaning | Next action |
|---|---|---|
| `not logged in: run tanomis login` | The context has no usable access or refresh state. | Run `tanomis login --name CONTEXT`, or repeat `--console` to refresh its configuration. |
| Refresh fails | The refresh grant is no longer usable or the identity provider is unavailable. | Retry only for a transient outage; otherwise log in again. |
| `no workspaces` | Authentication succeeded, but this person has no active organization membership. | Ask an organization owner to confirm membership. |
| An organization row shows tenant `(none)` | Membership exists, but this person has no attached tenant they may access. | Ask an owner to confirm the attachment and, for a developer or viewer, the grant. |
| The organization has several tenants and no single default | There is no unambiguous tenant choice. | Repeat `tanomis workspace use ORG_SLUG --tenant TENANT_ID` with a tenant from `workspace ls`. |
| The selected tenant's state is not `ready` | Its attachment is still provisioning. | Wait for it to be ready, then select and verify again. |
| The workspace lacks `deploy`, `run`, `view`, or `manage` | The projection or saved snapshot does not include the operation family. | Ask an owner to grant it, then repeat `workspace use` and `workspace show`; selecting another value cannot widen access. |
| HTTP `401` | No valid accepted credential. | Check `tanomis auth status`, then log in again. |
| HTTP `403` | The credential is valid, but this principal is not permitted for the selected projection or operation. | Have an owner verify membership, grant, and capability. |
| `--tenant` conflicts with the selected workspace | The explicit tenant and saved tenant differ. | Omit `--tenant`, select the intended workspace, or clear the selection. |
Application requests consume bearer tokens and workspace headers as described in
[Platform API request basics](/api#request-basics), but that reference does not
turn this human login into a backend credential.
## Where to go next
- [Quickstart](/getting-started/quickstart): deploy and run your first agent.
- [Workspaces](/guides/manage/workspaces): switching in the Console and the API.
- [Journey commands](/cli): exact CLI syntax.
---
Source: /getting-started/coding-agents
# Build with a coding agent
Teach Claude Code, Codex, or Cursor to scaffold, deploy, run, and govern Tanomis agents with platform skills and a documentation MCP server, installed by one tanomis command.
Most Tanomis agents are written with a coding agent beside the developer. `tanomis` meets it
halfway: four skills that teach the agent how the platform works and how to drive `tanomis`, and a
documentation server that lets it search and read these docs as tools. Both ship inside the
`tanomis` binary, so they always match the commands your version has.
- One command installs everything: `tanomis skills install`.
- Skills go to `.claude/skills`, `.agents/skills`, and `.cursor/skills` at the root of your
repository; the documentation server is registered in `.mcp.json` and `.cursor/mcp.json`.
- Every skill ends with a command reference rendered from your binary's own help text, so
re-run the install after upgrading `tanomis`.
- Included in Tanomis v0.2.0 (first shipped as `cog` v0.1.1). `tanomis version` shows which
you have, and the [installer](/getting-started/install) upgrades in place.
## Install the skills
From the root of the repository that holds your agents:
```bash
tanomis skills install
```
```text title="Output"
created .claude/skills/tanomis-build-agent/SKILL.md
created .claude/skills/tanomis-deploy-rollout/SKILL.md
created .claude/skills/tanomis-govern/SKILL.md
created .claude/skills/tanomis-run-debug/SKILL.md
created .mcp.json
created .agents/skills/tanomis-build-agent/SKILL.md
created .agents/skills/tanomis-deploy-rollout/SKILL.md
created .agents/skills/tanomis-govern/SKILL.md
created .agents/skills/tanomis-run-debug/SKILL.md
created .cursor/skills/tanomis-build-agent/SKILL.md
created .cursor/skills/tanomis-deploy-rollout/SKILL.md
created .cursor/skills/tanomis-govern/SKILL.md
created .cursor/skills/tanomis-run-debug/SKILL.md
created .cursor/mcp.json
To register the documentation server with Codex, add to ~/.codex/config.toml:
[mcp_servers.tanomis-docs]
command = "tanomis"
args = ["docs", "serve"]
```
Commit the files: they are plain Markdown and JSON, and teammates who clone the repository get
the skills without running anything. Re-running the command is safe. A skill file that `tanomis`
wrote and nobody edited is updated in place; one you edited, or one `tanomis` did not write, is
left alone and listed, unless you pass `--force`.
| Agent | Repository scope (default) | User scope (`--scope user`) |
|---|---|---|
| Claude Code | `.claude/skills/` | `~/.claude/skills/` |
| Codex | `.agents/skills/` | `~/.codex/skills/` |
| Cursor | `.cursor/skills/` | `~/.cursor/skills/` |
Pick agents with `--agent claude,codex` (default `all`), or write into any directory with
`--dir`. `tanomis skills list` names the skills; `tanomis skills show deploy-rollout` prints one without
installing it. The full flags are on [`tanomis skills install`](/cli/skills/install).
## What the skills cover
| Skill | What the agent learns to do |
|---|---|
| `tanomis-build-agent` | Scaffold with `tanomis agent new`, write the entrypoint, declare models, tools, hosts, and limits in `agent.yaml`, run the contract test, dry-run the deploy. |
| `tanomis-deploy-rollout` | Dry-run on a branch, deploy to the sandbox tenant, read version status, canary to named customer tenants with `tanomis agent rollout`, roll back, deploy from CI with an automation client. |
| `tanomis-run-debug` | Start sessions and runs from the CLI, read events and the trace, tell a policy refusal from a failure in the agent, unblock a run waiting on an approval, attach files. |
| `tanomis-govern` | Add approvals to the right actions, narrow egress, set the authorization ceiling, declare connectors and allowed tools, manage agent access, read the audit trail for a run. |
Each skill is a workflow with a checkpoint per step, a table of what to do when a step fails,
and a command reference generated from `tanomis --help` at install time. Each also states what it
will not do:
- Never edit tenant policy to make a run pass.
- Never widen a ceiling, an egress list, or a permitted-tools list beyond what the task needs,
and never without telling you which line changed and why.
- Never store a credential, token, or client secret in the repository, in `agent.yaml`, or in a
skill file.
## The documentation server
`tanomis docs serve` is a Model Context Protocol server over standard input and output with three
tools: `search_docs`, `read_doc`, and `list_docs`. It serves this site, fetched as one file on
first use and cached under `~/.config/tanomis/docs-cache` (`--cache-dir` moves it); the copy is
revalidated when it is older than an hour and served from the cache when the site cannot be
reached. It runs locally, needs no credential, and serves nothing this site does not publish.
```bash
tanomis docs serve
```
The install registers it as `tanomis-docs` where the agent reads a JSON file, and prints what to
add or run where it does not:
| Agent | Registration |
|---|---|
| Claude Code, repository | Merged into `.mcp.json` |
| Claude Code, user | `claude mcp add --scope user tanomis-docs -- tanomis docs serve` |
| Cursor | Merged into `.cursor/mcp.json` or `~/.cursor/mcp.json` |
| Codex | `[mcp_servers.tanomis-docs]` in `~/.codex/config.toml`, printed by the install |
Pass `--no-mcp` to skip the registration. Point the server at another copy of the docs with
`--docs-url`, or ask the site for a fresh copy with `--refresh`; see
[`tanomis docs serve`](/cli/docs/serve).
## Where to go next
- [Quickstart](/getting-started/quickstart): the flow the skills automate.
- [`tanomis skills`](/cli/skills) and [`tanomis docs`](/cli/docs) in the CLI reference.
- [Docs for LLMs](/llms-full.txt): the same file the documentation server indexes.
---
Source: /getting-started/console-tour
# Tour the Console
What each screen of the Tanomis Console is for, from the first session to the audit export, and when to reach for the CLI or the API instead.
The Console is the web front door to your workspace. It drives the same runtime path as the CLI and
the Platform API, so anything you do there is inspectable from the others. This tour walks the
navigation rail from top to bottom.
- **Sessions** is where you talk to an agent and answer its questions and approval requests.
- **Agents**, **Connectors**, **Control** are where you inspect and change what is deployed.
- **Policies**, **Assurance**, **Work** are where you see what was decided
and what is waiting for a person.
- **Settings** is your organization: members, roles, tenant grants, workspaces, **Machine clients**, and **Tenants**.
Owners and admins can [create automation credentials](/guides/deploy/automation-credentials)
and [manage tenants](/guides/manage/tenants-and-environments) here.
## Overview
The landing screen for the selected workspace: agent activity, what needs attention, the fleet of
enabled agents, connected systems, and the enforcement posture, including counts of actions denied
by policy and egress refused. Use it to see at a glance whether anything is waiting or failing.
## Sessions
A session is a conversation with one enabled agent version. The screen shows the session rail on
the left, the run timeline in the middle, and a composer to start the next run. When a run pauses
for a person, the approval or question renders inline as a card; answering it resumes the same run.
Each session also exposes its evidence, its delegations (work handed to other agents), and its token
usage. Forking a session starts a new lineage from a point in the timeline.
## Agents
Every agent enabled for the tenant, with its versions, the manifest each version was registered
with, and per-tenant enablement. This is where you confirm which version serves this tenant and read
the contract it carries: models, egress, policy ceiling, connectors, and trace capture.
## Connectors
Configure service connections and connect your own account where required. In **Agent → Integrations**,
select a version, choose a compatible connection, and **Allow access** as an administrator. Personal
integrations also need each user's own account. Setup preserves a blocked request; **Continue**
starts it only when ready. See [MCP connectors](/guides/connect-tools/mcp-connectors).
## Policies
What agents on this tenant may do, and what happened when they tried otherwise. **Rules** is the
live policy as a list of sentences (who it applies to, what it watches, what a match refuses) with
what each did in the last seven days. A rule opens on its own screen with a form and the same rule
as code side by side, and "what it would do" replayed over last week beside it. Edits are kept as
you type into one pending change; **Review and publish** shows the diff and the impact, and
publishes everything together as the next version. **Versions** rolls back or restores. The
Observing / Enforcing switch at the top carries the week's number. **Activity** is the feed of
decisions in plain words, each naming the rule, with "Why?" and "Make a rule". See
[Write tenant policy](/guides/govern/write-tenant-policy).
## Control
Operational controls for what is running: enablement per agent, session lifecycle (stop, resume),
and the list of live sessions. Use it when you need to stop serving a version or release a runtime
without touching the CLI.
## Assurance
The sealed ledger, the policy decisions, the controls an auditor tests, the chain's integrity, and
the execution traces. The ledger is filterable by period, agent, decision, and plane. An auditor
package can be exported from the Integrity tab. See [Audit and evidence](/guides/audit/audit-and-evidence)
for what each record proves.
## Supervision
**Supervision** is where people step into agent work. Its **Inbox** lists agent asks waiting for a person: every open permission request and
question across the sessions in the selected tenant, longest-waiting first, answerable in place.
"Mine" shows the sessions you started; "All in tenant" shows everyone's. Any member of the tenant
may answer; the first answer wins. Answering here is the same as answering in the session, and
opening the inbox never wakes a parked session.
## Work
**Work** is the queue of work the platform could not settle on its own, longest-waiting
first, with any settlement question a human still owes at the top.
## Evaluations
Where a tenant with evaluation enabled sees evaluation runs, their assessments, cost and latency,
and the annotated trace each verdict was made about. See [Evaluate a version](/guides/evaluate/evaluate-a-version).
## Settings
Your organization: members and roles, invitations, tenant grants, ownership, and the workspaces you
can select. See [Organization members and access](/guides/manage/organization-members).
## Console, CLI, or API?
| You want to | Use |
|---|---|
| Try an agent, answer approvals, read a run's evidence | Console |
| Author, deploy, roll out, script an operation | `tanomis` |
| Call an agent from your product | Platform API |
All three share one control plane, so a session started from the API appears in the Console and a
rollout from the CLI is visible under Agents.
## Where to go next
- [Quickstart](/getting-started/quickstart): deploy and run your first agent.
- [Answer approvals in the Console](/guides/run/answer-approvals-in-the-console)
- [Read the audit trail](/guides/audit/read-the-audit-trail)
---
Source: /getting-started/get-access
# Get access
How you get into Tanomis: an organization, an invitation, and a first workspace, and what to ask for if you are setting one up.
You reach Tanomis through an organization. Either someone invites you to one, or you are setting
one up for your company. Both paths end with a workspace you can select and an agent you can run.
- **Joining:** accept the invitation, sign in, select the workspace you were granted.
- **Setting up:** Tanomis creates the organization and its first tenant with you as owner; you
invite your team and grant tenants.
- Sign-in is through the hosted identity provider. Enterprise single sign-on is planned.
## Join an organization
1. Open the invitation link from the email, or run `tanomis org accept --token TOKEN`.
2. Sign in. The hosted login is a browser flow; the CLI's device flow prints a code to enter.
3. If the organization has a default tenant, you are granted it on acceptance. Otherwise an owner
grants you a tenant.
4. Select the workspace: `tanomis workspace use ORG --tenant TENANT`, or open the Console.
From here, [Quickstart](/getting-started/quickstart) if you are building, or
[Tour the Console](/getting-started/console-tour) if you are reviewing.
## Set up an organization
Creating an organization and provisioning its first tenant are platform operations performed with
Tanomis. Ask for:
- the organization name and slug;
- the first customer tenant, and whether it should be the default;
- a sandbox tenant beside it for testing, if you want one from the start;
- the owner: you.
Once it exists, [invite your team](/guides/manage/organization-members), grant tenants, and
deploy. Owners and admins can create additional tenants and offboard them in
**Settings → Organization → Tenants**. Attaching an existing tenant and changing the default
remain operator operations; see [Customer tenants](/guides/manage/tenants-and-environments).
## Where to go next
- [Authenticate and select a workspace](/getting-started/authenticate-and-select-workspace)
- [Organizations and tenants](/concepts/organizations-and-tenants)
---
Source: /getting-started/install
# Install tanomis
Install the tanomis CLI on macOS, Linux, or Windows with one command, verify the download, pin a version in CI, and upgrade.
The `tanomis` CLI is how you author, deploy, roll out, and inspect agents from a terminal or a
pipeline. It is a single static binary with no dependencies.
- macOS and Linux: `curl -fsSL https://get.tanomis.com/install.sh | sh`
- Windows: `irm https://get.tanomis.com/install.ps1 | iex`
- Every release ships with `SHA256SUMS` and a keyless cosign signature; the installer verifies
the checksum before it installs anything.
- Pin a version in CI with `TANOMIS_VERSION=v0.2.0`.
## macOS and Linux
```bash
curl -fsSL https://get.tanomis.com/install.sh | sh
```
The installer detects your operating system and architecture (amd64 or arm64), downloads the
release archive and its checksums, verifies the SHA-256, and installs `tanomis` to `/usr/local/bin`
when that is writable, otherwise to `~/.local/bin`. It tells you if that directory is not on your
`PATH`.
```text title="Output"
Installing tanomis v0.2.0 for darwin/arm64 from https://get.tanomis.com/tanomis/v0.2.0
Installed: tanomis v0.2.0 (commit , built ) -> /usr/local/bin/tanomis
Next: tanomis login --console https://api.tanomis.com --name saas
```
| Variable | Effect |
|---|---|
| `TANOMIS_VERSION` | Install this release instead of the latest, for example `v0.2.0`. |
| `TANOMIS_INSTALL_DIR` | Install into this directory instead of the default. |
| `TANOMIS_ARCH` | Override the detected architecture, `amd64` or `arm64`. |
The former `COG_VERSION`, `COG_INSTALL_DIR`, and `COG_ARCH` settings remain fallbacks when their
`TANOMIS_*` counterparts are unset. The installer also writes a `tanomis` compatibility shim that
prints `cog: deprecated, use tanomis` to stderr and forwards arguments to `tanomis`. An unrelated
existing `tanomis` executable is left in place.
## Windows
```powershell
irm https://get.tanomis.com/install.ps1 | iex
```
The script installs `tanomis.exe` to `%LOCALAPPDATA%\Programs\tanomis`, adds that directory to your user
`PATH`, and verifies the zip's SHA-256 the same way. Open a new terminal afterwards. The same
`TANOMIS_VERSION` and `TANOMIS_INSTALL_DIR` variables apply.
## Verify a download by hand
Every release directory carries `SHA256SUMS`, signed keyless with cosign by the release
workflow. To verify a download yourself:
```bash
base=https://get.tanomis.com/tanomis/v0.2.0
curl -fsSLO "$base/SHA256SUMS"
curl -fsSLO "$base/SHA256SUMS.sig"
curl -fsSLO "$base/SHA256SUMS.pem"
cosign verify-blob --signature SHA256SUMS.sig --certificate SHA256SUMS.pem \
--certificate-identity-regexp '^https://github.com/anyfind/agent-platform/' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com SHA256SUMS
curl -fsSLO "$base/tanomis_v0.2.0_linux_amd64.tar.gz"
sha256sum --check --ignore-missing SHA256SUMS
```
## Pin a version in CI
Installing in a job is the same one-liner with the version pinned, so a release never changes a
pipeline under you:
```yaml title=".github/workflows/agent.yml"
- run: curl -fsSL https://get.tanomis.com/install.sh | sh
env:
TANOMIS_VERSION: v0.2.0
TANOMIS_INSTALL_DIR: /usr/local/bin
```
See [Deploy from CI](/guides/deploy/deploy-from-ci) for the rest of the job and
[Automation credentials](/guides/deploy/automation-credentials) for its login.
## Upgrade
Run the installer again. It replaces the binary in place, and `TANOMIS_VERSION` unset means the
latest release. `tanomis version` prints the installed version, commit, and build date.
## Shell completion
```bash
tanomis completion zsh > "${fpath[1]}/_tanomis" # zsh
tanomis completion bash > /etc/bash_completion.d/tanomis
tanomis completion fish > ~/.config/fish/completions/tanomis.fish
```
## Where to go next
- [Get access](/getting-started/get-access): a workspace to sign in to.
- [Authenticate and select a workspace](/getting-started/authenticate-and-select-workspace):
the first login.
- [Quickstart](/getting-started/quickstart): deploy and run your first agent.
---
Source: /getting-started/quickstart
# Quickstart
Scaffold an agent, deploy it once for every tenant on your platform, run it, and open the result in the Console.
Deploy your first agent to every tenant
Scaffold an agent, deploy it once for every tenant on your platform, run it, and open the result
in the Console. About ten minutes, four commands.
- `tanomis` [installed](/getting-started/install) and signed in as a platform operator ([Get access](/getting-started/get-access))
- Python 3.12 or newer and [`uv`](https://docs.astral.sh/uv/) on your `PATH`
- A directory in your own repository
The journey is **Prepare → Scaffold → Validate → Deploy → Run**.
## 1. Check you are signed in
Confirm the context, and see the tenants the deploy will reach:
```bash
tanomis auth status
tanomis tenant ls
```
```text title="Output"
context: saas logged-in: yes expires in 54m
TENANT STATE
northwind-sandbox active
bluewater-bank active
orion-retail active
helix-health active
```
**Checkpoint: signed in.** You should see `logged-in: yes` and at least one `active` tenant.
Your customers are tenants; the sandbox is the one that serves no customer, and it is where you
will run the agent in step 5.
## 2. Scaffold a working agent
One command gives you a Claude Agent SDK project that already answers. You will make it yours
after it runs.
```bash
tanomis agent new support-copilot --harness claude-agents-sdk --agents-root ./agents --description "Triage and draft responses to support requests"
```
```text
agents/support-copilot/
├── agent.py create_agent(input, context) returns (prompt, options)
├── agent.yaml what the agent may do: model, hosts, tools, limits
├── pyproject.toml
├── uv.lock
└── tests/test_agent_contract.py
```
Open `agent.py` and change one line, the `system_prompt`, to "Triage support requests: severity,
summary, and a concise draft reply." Everything else stays. Then run the contract test:
```bash
cd agents/support-copilot
uv run python -m unittest tests/test_agent_contract.py
```
**Checkpoint: project ready.** You should see `OK`. The test checks the entrypoint's shape; your
prompt logic is plain Python and needs no runtime to import.
## 3. Dry-run the deploy
See exactly what would be built and where it would be enabled, without uploading a byte. This is
the same command you will put on every pull request.
```bash
uv lock --locked
tanomis agent deploy . --scope global --tenants all --dry-run
```
```text title="Output"
dry-run: tanomis agent deploy .
name: support-copilot
version: 0.1.0
scope: global
tenants: all (active customer tenants snapshotted by the server)
build profile: python-managed
lock policy: uv-frozen
files: 5
checksum: sha256:9f1c…e2a0
no changes made (dry-run) — no build was created, no bytes were uploaded
```
**Checkpoint: package ready.** You should see "no changes made". Commit `uv.lock`; remote builds
only accept locked projects.
## 4. Deploy to every tenant
One command builds remotely, registers an immutable global version with the contract you
declared, takes a warm-start snapshot, and enables the version for every active tenant. Nothing
routes traffic until that last step.
```bash
tanomis agent deploy . --scope global --tenants all
```
```text title="Output (abridged)"
build bld_3f9a2c created for support-copilot@0.1.0 (global, tenants all, profile python-managed, lock uv-frozen)
upload done
build done image sha256:7ab3…
register done support-copilot@0.1.0
golden ready sessions will warm-start
enable done 12 tenants → 0.1.0
```
> **Not a platform operator?** Organization members deploy to their own tenants with
> `--tenant TENANT` instead of `--scope global --tenants all`. The rest of this page is the same.
Confirm what each tenant now serves:
```bash
tanomis agent versions support-copilot --by-tenant
```
**Checkpoint: version enabled.** You should see `enable done` with the tenant count, and every
tenant on `0.1.0` in the versions table. A tenant created later is enabled with `tanomis agent enable`.
A skipped golden is fine: the version still runs, with cold starts.
## 5. Run it
Invoke the agent in the sandbox tenant through the same managed path the Console and your applications
use. The command opens a session, starts a run, and streams its events.
```bash
tanomis session run support-copilot \
--tenant northwind-sandbox \
--prompt "Customer reports that CSV exports stop at 80%. Draft a first response."
```
```text title="Output"
session ses_7d4f9c run run_a12bc3 version support-copilot@0.1.0
run_started 12:04:01
agent_message Thanks for flagging this. Exports stopping at 80% usually means ...
run_completed 12:04:09 status=succeeded
```
**Checkpoint: first run verified.** You should see `run_completed` with `status=succeeded`. If the
stream closes without it, the run resource in the Console or API is the authority.
Console · Sessions · ses_7d4f9c · run_a12bc3
Open the run in the Console. The same events are there, plus who invoked it, the policy decisions the run passed, and the execution trace. This is the record every run gets; see [Tour the Console](/getting-started/console-tour).
## What you just built
The lifecycle from the [Overview](/), with every stage now real.
1. **Source** agents/support-copilot in your repository.
2. **Version** support-copilot@0.1.0, global, immutable.
3. **Enablement** Every customer tenant serves 0.1.0.
4. **Session** ses_7d4f9c, warm-started.
5. **Run** run_a12bc3, succeeded.
6. **Evidence** Ledger entry and trace, in the Console.
## Next
Make it yours
[Write your agent](/guides/build/write-your-agent)
Prompts, tools, and input handling in agent.py; what the harness gives you.
Give it tools
[MCP connectors](/guides/connect-tools/mcp-connectors)
Declare a connector and require confirmation for selected tool calls.
Ship the next version
[Roll out and roll back](/guides/deploy/rollout-and-rollback)
Canary 0.2.0 to a few tenants, graduate it, or restore the baseline in one command.
If something goes wrong
| Symptom | Next action |
|---|---|
| Login required or tenant denied | Run `tanomis login`, then `tanomis auth status`. An administrator confirms your grant. |
| Lockfile missing or stale | Run `uv lock`, commit `uv.lock`, repeat the dry run. |
| Manifest rejected | Restore the generated fields; read [Manifest essentials](/guides/build/manifest-essentials) before widening anything. |
| Build fails after a clean dry run | Read the failing phase in the output, fix the dependency or source error, deploy again. |
| First run is slow | The golden was skipped; the version cold-starts and is still usable. |
| A model or tool action is denied | Compare with `permitted_models`, the approval list, and tenant policy. Refusals are recorded in the run. |
---
Source: /guides/audit/audit-and-evidence
# Audit and evidence
Read the organization's audit trail, understand what the ledger proves, and find the execution record for a run.
Every governed action on Tanomis leaves a record. This guide shows where to read it and what each
record can and cannot prove.
- The **audit trail** is the organization's tamper-evident record of decisions: who did what,
which policy allowed it, which approvals were given, which versions were enabled where.
- The **run record** is the authoritative outcome of each run, with its events replayable by id.
- The **execution trace** is the detailed record of what a run did, kept separately from the
audit trail so evidence and debugging data can have different retention and access.
## Read the audit trail
Owners and admins can read the organization's trail from the Console or the CLI:
```bash title="Terminal"
tanomis org audit
tanomis org audit --limit 200
tanomis org audit --cursor "$NEXT_CURSOR"
```
Entries include membership and grant changes, invitations, enablement changes such as a rollout or
a `disable`, approval decisions, and delegation decisions. Each entry names the principal, the
tenant where relevant, the action, and the decision. Content such as prompts or tool payloads is
never in the trail; where content matters to a decision, the entry references it by hash.
## What the ledger proves
The trail is append-only, hash-chained, and signed per tenant. A valid chain demonstrates that the
record has not been altered or truncated since it was written. It is privacy-by-reference: an
auditor can verify that a decision was made about specific content without the ledger holding
that content.
The ledger is the platform's own record. It proves what Tanomis decided and recorded, not what
happened inside another system afterwards. To prove an external effect, read that system.
## Read a run's record
The run resource is the authoritative status of one unit of work, and its events can be replayed
by id at any time while history is retained:
```bash title="Terminal"
tanomis session get "$SESSION_ID"
curl "$TANOMIS_API/v1/sessions/$SESSION_ID/runs/$RUN_ID/events" \
-H "Authorization: Bearer $TANOMIS_TOKEN" \
-H "X-Organization-ID: $TANOMIS_ORG" \
-H "X-Workspace-Tenant-ID: $TANOMIS_WORKSPACE" \
-H "Accept: text/event-stream" \
-H "Last-Event-ID: 0"
```
Replay starts after the id you give, so `Last-Event-ID: 0` reads a run from the beginning. Use the
run's terminal event and status together: a stream can end without a terminal frame, and the run
resource is what decides.
## Find the execution trace
Each run emits one execution trace: model calls, tool calls, timing, and content captured at the
level the tenant's capture policy allows (a reference, a redacted copy, or full content with
approval). The trace shares the run and session ids with the audit trail, so a decision in the
trail can be matched to the execution it was made about.
Trace access is tenant-scoped and gated by the tenant's policy at read time. Ask your
administrator which capture level your tenant runs and where its traces are served.
## Where to go next
- [Policy, approvals, and evidence](/concepts/governance): the model behind these records.
- [Stream and replay events](/guides/run/stream-and-replay-events): the replay contract in
detail.
- [Organization members and access](/guides/manage/organization-members): who can read the trail.
---
Source: /guides/audit/export-an-auditor-package
# Export an auditor package
Hand an auditor a self-contained export of the ledger segment they need, with the integrity metadata to check it.
An auditor package is an export of a slice of the audit trail with the integrity metadata needed
to check it: the entries, their hash chain, and the signatures. It is built for someone outside
your organization who needs to verify what happened without access to your workspace.
- Export from **Console → Assurance → Integrity → Export auditor package**, scoped by tenant and time range.
- The package contains ledger entries and integrity metadata, never prompt or tool payload bodies.
- A valid chain proves the record is intact; it does not by itself prove an external system
changed. Read that system for effects.
## Export
1. Open Assurance in the Console for the tenant in question.
2. Filter to the agents, decisions, or time range the request covers.
3. Choose Export auditor package. The export runs in the background; the screen reports when it
is ready and where to download it.
Owners and admins can export. The export itself is an audited action.
## What is inside
| Part | Purpose |
|---|---|
| Ledger entries | The decisions, attributions, approvals, and enablement changes in scope |
| Chain metadata | Each entry's hash and its link to the previous entry |
| Signatures | The tenant's signing material so the chain can be checked independently |
| Content references | Hashes of the content decisions were about, so an auditor can match content you provide separately without the package carrying it |
## What an auditor can conclude
The package demonstrates that the entries are complete for the range and unaltered since they
were written, and that each decision was attributed to a principal and a version. Independent
assurance beyond that depends on the anchor and key custody your environment uses; an offline
verifier is planned, see [Verify the ledger independently](/guides/audit/verify-the-ledger).
## Where to go next
- [Audit and evidence](/guides/audit/audit-and-evidence)
- [Read the audit trail](/guides/audit/read-the-audit-trail)
---
Source: /guides/audit/policy-decisions
# Policy decisions
Where each allow or deny lands, what a decision record contains, and how to trace a refusal back to the ceiling that caused it.
Every governed action passes a decision point before its side effect: the gateway for models and
connector tools, the egress proxy for external hosts, the control plane for delegation, the
runtime for approvals. Each decision is recorded once, and the Console lists them under
**Policies → Activity**.
- A decision record names the subject, the action, the resource, the verdict, and the policy or
ceiling that decided it.
- Verdicts are `allow`, `deny`, or `require-approval`. An approval that was asked for is a
decision; the person's answer is a second record.
- A verdict is not an outcome. Each record also carries the enforcement outcome (`allow`,
`would-deny`, `deny`, `configuration-deny`, `error-open`) and the posture it was decided under
(`shadow` or `enforce`). A `deny` recorded as `would-deny` under `shadow` stopped nothing: the
Console reads it as "Would have blocked · the request ran anyway".
- A decision that could not be made, for example because facts were missing, is recorded as
such rather than silently allowed.
## Where decisions are made
| Action | Decided at | Typical refusal |
|---|---|---|
| Model call | The LLM gateway | Model outside `permitted_models` or tenant policy |
| Connector tool call | The connector gateway | Tool outside the declared allowlist, connection not ready, current grant revoked |
| External connection | The egress proxy | Host outside `network.egress` or the tenant ceiling |
| Delegation | The control plane | No grant, or scope outside both ceilings |
| Approval-required tool | The runtime | Waits for a person; the answer is the decision |
## Read a decision
Open **Policies → Activity** in the Console, or open the Decisions tab of Assurance. Each record shows the
verdict, the reason, and the identifiers of the policy or ceiling behind it, plus the run and
session it belongs to. A refusal in a run's event stream and its decision record share the run id.
## From a refusal to its cause
1. Read the decision's reason and the control it names.
2. If it names the version's own contract, the fix is a new version: widen `permitted_models`,
`network.egress`, or the connector allowlist, and deploy.
3. If it names a tenant ceiling or tenant policy, the fix is administrative: the ceiling must
change before any version can pass.
4. If it is an approval, nothing is wrong: a person must answer. See
[Approval-required actions](/guides/run/approval-required-actions).
Do not "fix" a transient platform error by widening a ceiling; a policy denial and an
infrastructure failure are different records.
## Where to go next
- [Policy, approvals, and evidence](/concepts/governance): the ceilings in one map.
- [Read the audit trail](/guides/audit/read-the-audit-trail)
---
Source: /guides/audit/read-the-audit-trail
# Read the audit trail
Find what happened, who did it, and under which authority, from the Console's Assurance section or from tanomis org audit.
The audit trail is the organization's record of decisions. Owners and admins read it in the
Console under Assurance, or page through it with `tanomis org audit`. Every entry names an actor, an
action, the tenant where relevant, and the decision.
- **Console → Assurance** filters by agent, decision, and plane, and opens the ledger entry.
- **`tanomis org audit`** pages the same trail from a terminal, newest first.
- Content is never in the trail: an entry references what a decision was about by hash.
- Members without the owner or admin role see an explanation instead of the trail.
## From the CLI
```bash title="Terminal"
tanomis org audit
tanomis org audit --limit 200
tanomis org audit --cursor "$NEXT_CURSOR" -o json
```
Use `-o json` in scripts; the cursor in the response continues the page.
## What you will find
| Family | Examples |
|---|---|
| Membership | Invitations sent, accepted, revoked; roles changed; ownership transferred |
| Access | Agent connector access enabled and disabled |
| Deployment | Versions registered, promoted, enabled, disabled, retired |
| Runtime decisions | Policy allow and deny verdicts, egress refusals, model refusals |
| Approvals | Permission requests, who answered, what they decided |
| Delegation | Each `agent.delegate` attempt, admitted or refused, with the authority chain |
| Support | Actions taken under a support grant, with the grant |
## Read one entry
An entry carries the principal that acted, the agent version where one acted, the run and session
ids where the action belongs to a run, the policy and decision, and hashes of any content the
decision was about. The run and session ids are how you get from an entry to the execution trace
and back; see [Traces and content classes](/guides/audit/traces-and-content-classes).
## From the Console
The Ledger tab of Assurance lists entries across the selected tenant with filters for agent, decision, and plane.
Selecting an entry opens the ledger record. Decisions shows the policy decisions on their own with
a link into the ledger entry for each; see [Policy decisions](/guides/audit/policy-decisions).
## Where to go next
- [Audit and evidence](/guides/audit/audit-and-evidence): what the ledger proves.
- [Export an auditor package](/guides/audit/export-an-auditor-package): handing the record to
someone outside your organization.
---
Source: /guides/audit/traces-and-content-classes
# Traces and content classes
Match a ledger decision to the execution it was made about, and understand which content class you are allowed to see when you do.
The ledger says what was decided; the execution trace says what the agent did. They are kept apart
on purpose and joined by the run and session identifiers, so an auditor can move from a decision
to the execution behind it, at the content level the tenant permits.
- Every ledger entry that belongs to a run carries the run and session ids; every trace span
carries the same.
- Content in a trace is classified: `reference`, `redacted`, or `full`. The class you are served
is decided at read time from the tenant's current policy.
- A ledger entry references trace content by hash, so a decision can be checked against the
content without the ledger holding it.
## From a decision to a trace
1. In Assurance, open the decision and choose its trace link. Traces live in the Quality section's
Traces tab, where evals grade them, and the link opens the run's trace there.
2. To browse instead, open Quality, then Traces, and look the run up by session or trace id.
3. The span for the decided action, a tool call or a model call, shows the decision's identifiers
beside the captured content.
The Sessions screen offers the same path from the other side: a session's evidence panel lists its
runs' decisions and links into the trace.
## Content classes
| Class | What you see | Who decides |
|---|---|---|
| `reference` | Structure and hashes: which calls happened, in what order, with what outcome | Tenant capture policy |
| `redacted` | Bodies with sensitive spans removed by tenant redaction rules | Tenant capture policy |
| `full` | Complete bodies | Tenant capture policy, and an approval to serve |
The class is decided inside the sandbox when the content is captured, from the tenant's policy,
and rechecked when you read. The server derives the class from what it captured, never from a
label the agent wrote, so an agent cannot promote content into a class it is not allowed to be.
## Matching content by hash
A ledger entry about content carries the content's hash. If you hold the content, from a `full`
trace or from your own system, hash it and compare. This is how the ledger stays free of prompt
and payload bodies while still binding decisions to exact content.
## Where to go next
- [Capture execution traces](/guides/evaluate/capture-traces): what a trace contains.
- [Audit and evidence](/guides/audit/audit-and-evidence)
- [Data handling and retention](/guides/govern/data-handling): planned page on storage and
retention.
---
Source: /guides/audit/verify-the-ledger
# Verify the ledger independently
Check the audit ledger's hash chain and signatures with an offline verifier you run yourself.
The audit ledger is append-only, hash-chained, and signed. An offline verifier that an auditor runs outside Tanomis is planned.
This page is planned and not yet written, because the capability it describes is not yet
generally available. The structure is here so you can see where it fits; the notice comes off
when it ships.
## What this page will cover
- Export a ledger segment.
- Run the verifier and read its result.
- What an anchor proves and what it does not.
## Meanwhile
- [Audit and evidence](/guides/audit/audit-and-evidence) explains what the ledger proves today.
---
Source: /guides/build/agent-skills
# Add skills to your agent
Ship a folder of SKILL.md files with your agent so the model loads the right procedure on demand, on the Claude Agent SDK and Deep Agents harnesses.
A **skill** is a folder with a `SKILL.md` in it: a name, a description, and instructions
for one job. The model reads only the names and descriptions up front, and pulls the full
instructions in when a request calls for that job. That is what lets you ship a dozen
detailed procedures without spending a dozen procedures' worth of context on every run.
Put the folder in your agent, deploy, and the platform delivers it to the harness.
- Skills live in `skills/` next to your `agent.yaml`, and ship inside your agent's image.
- The platform mirrors them into the session workspace at `.claude/skills/` on every run.
- **Claude Agent SDK:** shipping the folder is enough.
- **Deep Agents:** pass `backend=` and `skills=` to `create_deep_agent`.
- The mirror is platform-managed. The image is the source of truth, and anything written
into the mirror at run time is replaced on the next run.
## Write a skill
```text title="Your agent bundle"
my-agent/
├── agent.py
├── agent.yaml
├── pyproject.toml
└── skills/
└── release-notes/
└── SKILL.md
```
The directory name is the skill name, and it has to match the `name` in the frontmatter:
```markdown title="skills/release-notes/SKILL.md"
---
name: release-notes
description: Turn a list of merged pull requests into release notes grouped by change type. Use when asked for release notes, a changelog, or a summary of what shipped.
---
# Release notes
Turn merged pull requests into notes a reader can skim.
## Steps
1. Group the PRs by type: Added, Changed, Fixed, Removed. Drop anything purely
internal unless the user asks for the full list.
2. Write one line per entry, starting with a past-tense verb. Say what changed for
the reader, not which files moved.
3. Put breaking changes first, under their own **Breaking** heading.
## Output
Markdown, one `##` heading per group. No preamble and no closing summary.
```
`description` is the only thing the model sees before deciding whether to open the skill,
so it carries the whole routing decision. Say what the skill does **and when to reach for
it**. "Release notes helper" gives the model nothing to match a request against; the
description above names the words a user would actually type.
Anything else in the folder (reference files, scripts, templates) ships with it, and the
model can read those once the skill is open.
Names are restricted to letters, digits, `.`, `_` and `-`. A name outside that set fails
the run, because skill names are passed through to the harness as tool-permission entries.
## Ship it
Nothing to declare in the manifest. The `skills/` directory's existence is the declaration:
```bash
tanomis agent deploy . --tenant acme-prod
```
Your agent's code is baked in at `/agent`, and on each run the platform mirrors
`/agent/skills/` into the session workspace at `/.claude/skills/`. That copy is
what the harness reads, because a run cannot read your image directly: the agent's working
directory is the session workspace, and paths outside it are refused.
The image is the source of truth. A skill you delete from the image disappears from the
mirror on the next run, and anything written into `.claude/skills/` during a run is
replaced. The platform compares the mirror against what it wrote, so an edit made by the
agent does not survive, by design: skills are instructions the model follows, and a
self-modifying skill tree would be a way for injected text to persist across runs.
Two bounds are enforced before anything is written: **500 files** and **5 MB** total. Over
either one, the run fails and names the cap it hit. It fails rather than truncating on
purpose, because a half-present skill tree is the kind of problem that only shows up the
first time someone needs the skill.
## Claude Agent SDK
Shipping the folder is the whole job. Project scope is on by default, so the CLI discovers
the mirror and the model can use the skills on the next run.
To narrow which of your shipped skills are available on a given run, set `skills`:
```python title="agent.py"
def create_agent(input_payload, context):
return (
input_payload.get("prompt", ""),
{
"system_prompt": "You write release notes.",
"skills": ["release-notes"], # or "all"
},
)
```
Options can be a plain dict, which keeps your module importable without the runtime. See
[Claude Agent SDK](/guides/build/claude-agent-sdk) for the full authoring shape.
`skills` decides what the model is *shown*. An unlisted skill is hidden from the listing
and refused by the Skill tool, but its files are still in the workspace and still readable
by any tool that can read files. It is a context-management control, not a security
boundary. To keep something away from the agent, do not ship it in the image.
Leaving `skills` unset is not "skills off" either. Everything in the mirror is discovered.
Set it to a list when you want a narrower set.
Changing `skills` partway through a session is rejected, because it reshapes the
conversation. Start a new session to change the set.
### Frontmatter that triggers an approval prompt
Keep your frontmatter to `name` and `description` unless you need more. A `SKILL.md` that
declares `allowed-tools`, `disallowed-tools`, `hooks`, or an execution context is treated
by the CLI as a skill that needs confirmation, so **every invocation raises a
human-in-the-loop approval** instead of running straight through. In an unattended run
that surfaces as a denial rather than a pause.
If you need one of those fields, either expect the prompt or name the skill explicitly in
`skills`, which registers it as pre-allowed.
## Deep Agents
You compile your own graph, so the wiring is yours. Pass **both** `backend=` and `skills=`
to `create_deep_agent`:
```python title="agent.py"
def create_agent(input_payload, context):
from deepagents import create_deep_agent
from deepagents.backends import FilesystemBackend
backend = FilesystemBackend(
root_dir=str(context.workspace_root),
virtual_mode=True,
)
skills_root = context.workspace_root / ".claude" / "skills"
return create_deep_agent(
model=context.runtime_context.model,
tools=[...],
backend=backend,
skills=["/.claude/skills/"] if skills_root.is_dir() else None,
)
```
`SkillsMiddleware` registers no tools of its own. It lists each skill and tells the model
to `read_file` the path it advertises, and that read goes through **the graph's** backend.
So building the middleware yourself and passing it through `middleware=` compiles fine,
lists your skills, and then leaves the model unable to open any of them. The failure is
quiet: the skills appear in the listing and every attempt to load one comes back empty.
Passing `skills=` to `create_deep_agent` is what avoids it, because that builds the
middleware against the same `backend` the file tools use.
`skills` takes backend paths, not filesystem paths, which is why the backend is rooted at
the workspace and the source is the path within it. `virtual_mode=True` is what makes
`/.claude/skills/...` resolve as a backend path; it is a path-mapping mode rather than a
sandbox, so treat it as wiring, not as a containment control.
The `is_dir()` guard keeps the agent working when there is no `skills/` directory, during
local development or if you remove it later.
## Check that it loaded
Every run with skills present emits an `agent_skills_loaded` event naming what was found,
so "did my skill actually ship?" is answerable from the run's event stream:
```json
{
"type": "agent_skills_loaded",
"source": "agent_service",
"data": {
"skills": ["release-notes"],
"file_count": 1,
"size_bytes": 742,
"changed": true,
"warnings": []
}
}
```
`skills` lists only the directories that can actually produce a skill. A directory with no
`SKILL.md`, no frontmatter, no `description`, or a `name` that does not match its directory
is left out and reported in `warnings` instead, because a harness would drop it and the
event should not claim otherwise. `SKILL.md` is case-sensitive.
If the event is missing entirely, the agent shipped without a `skills/` directory. Check
that it sits beside `agent.yaml` and that your build did not exclude it.
## Skills, tools, and connectors
Skills are instructions, not capabilities. A skill can tell the model *how* to use a tool,
but it cannot grant access to one. What the agent may call is decided by `policy`, by the
connectors attached to the version, and by the authorization ceiling, none of which a skill
can widen.
That is worth keeping in mind when you write one: a skill that says "call the GitHub API"
does nothing unless a connector supplying that tool is attached.
- **This page:** skills you ship *inside* your agent, for the agent's own model to use.
- [`tanomis skills install`](/getting-started/coding-agents): skills installed into *your*
coding agent (Claude Code, Codex, Cursor) so it can drive the platform CLI. Nothing to do
with your agent's runtime.
- [`delegation.skills`](/reference/agent-manifest): the units of work your agent *accepts*
from other agents, declared in the manifest. A delegation contract, not a folder of
instructions.
## Related
- [Session workspace and state](/guides/build/session-workspace-and-state): what else lives
in the workspace and what survives a resume.
- [Claude Agent SDK](/guides/build/claude-agent-sdk) and [Deep Agents](/guides/build/deep-agents):
the harness guides.
- [Agent manifest](/reference/agent-manifest): every field, including `delegation.skills`.
---
Source: /guides/build/choose-a-harness
# Choose a harness
Claude Agent SDK or Deep Agents. What each one is good at, what your entrypoint returns, and what stays the same whichever you pick.
A harness is the agent framework your code runs inside. Tanomis supports two Python harnesses, and
they share everything that is not your code: the manifest, the build, versions, tenant enablement,
the session runtime, governed tools, and evidence. Pick by how you want to express the agent.
- **Claude Agent SDK**: your entrypoint returns a prompt and options. The smallest entrypoint,
Claude's tools, hooks, subagents, and permission callbacks.
- **Deep Agents**: your entrypoint returns a compiled LangGraph graph. Typed LangChain tools,
middleware, structured output, checkpoint-aware state.
- Both are declared with one line in `agent.yaml`, `framework`, and scaffolded by `tanomis agent new`.
- Switching harness is a new agent, not a new version; `framework` is part of the contract.
## The choice
| | `claude-agents-sdk` | `deepagents` |
|---|---|---|
| Best fit | A system prompt plus tools, or an existing Claude Agent SDK agent | Tool-heavy or graph-shaped work, or existing LangGraph code |
| Entrypoint returns | `(prompt, options)` | A graph from `create_deep_agent` |
| Tools | Claude Agent SDK tools, hooks, subagents | Typed LangChain tools and middleware |
| State between turns | Claude SDK session continuity | LangGraph checkpoints in the session workspace |
| Approvals and questions | `permission_mode: "default"` plus the manifest's list | Manifest list composed with author `interrupt_on` |
Choose Claude Agent SDK when the behaviour is naturally a prompt with options and you want the
least code. Choose Deep Agents when the agent is a graph of typed tools, needs structured output
or middleware, or already exists as LangGraph code.
## What does not change
The runtime owns tenant isolation, the working directory, the resolved model and provider
credentials, session identifiers, governed connectors, and persistence. You own the instructions,
the tool implementations, and the behaviour the manifest permits. The
[entrypoint contract](/guides/build/write-your-agent) is the same shape for both.
List the harnesses your installed CLI supports rather than copying a value from memory:
```bash title="Terminal"
tanomis agent harnesses
```
## Where to go next
- [Claude Agent SDK](/guides/build/claude-agent-sdk): the entrypoint, the ownership boundary,
approvals.
- [Deep Agents](/guides/build/deep-agents): the graph entrypoint, dependencies, tool interrupts.
- [Write your agent](/guides/build/write-your-agent): what comes in and what you return.
---
Source: /guides/build/choose-models
# Choose models
Declare the model an agent uses and the models a run may request, and understand what the tenant's LLM gateway does with every call.
An agent never talks to a model provider directly. Every model call leaves the sandbox through the
tenant's LLM gateway, which is where model access is authorized, usage is metered per run, and
traffic is traced. Your manifest declares which models the agent may use; the gateway enforces it.
- `model` is the default the agent runs with. `permitted_models` is the allowlist a run may
choose from.
- A run that requests a model outside the allowlist is refused at the gateway.
- Model IDs are the ones available to your tenant. The same manifest can deploy to tenants with
different model lineups only if the IDs exist in each.
- Usage is metered per run, so cost and spend are attributable to a session, a run, and the
principal that started it.
## Declare the models
```yaml title="agent.yaml"
model: "your-workspace-model-id"
permitted_models:
- "your-workspace-model-id"
- "your-workspace-fast-model-id"
policy:
scope_ceiling:
- "llm:your-model-family:invoke"
```
- **`model`** is resolved for a run unless the request overrides it.
- **`permitted_models`** bounds overrides. Leaving it empty does not widen access: it means the
manifest declares no allowlist and the decision falls to platform and tenant policy.
- **`policy.scope_ceiling`** must carry the `llm::invoke` entry for the model family the
agent calls. Change the model, update the ceiling, or the declared ceiling stops describing what
the agent does.
## Follow the organization default
Set `model: default` to run on your organization's default model, chosen on the Console's
Models page (or with `tanomis model default set`). The default can be any active model on that
page: one of your organization's own models or one of the platform's. Until the organization
chooses one, `default` is the platform default. Each new session resolves `default` to the model
it points at then, so changing the default moves every agent that follows it without a new
version; open sessions keep the model they started with. If the chosen model stops being
available, sessions that follow the default are refused with the reason, and the Models page
warns, until someone chooses again. A concrete model ID pins the agent to that model, whatever the
default is.
```yaml title="agent.yaml"
model: default
permitted_models:
- default
```
`permitted_models` may list `default` alongside concrete IDs; it admits the one model the default
resolves to for the session. `tanomis agent new` scaffolds `model: default` for the Claude harness.
## Override per session or run
A session can be created with a `model` from the permitted set, and a run can request one the
same way. An override outside the set is a `400` at request time, before any work starts.
```json title="POST /v1/sessions"
{ "agent_name": "support-copilot", "model": "your-workspace-fast-model-id" }
```
## What the gateway does with a call
| Concern | What happens at the gateway |
|---|---|
| Authorization | The model is checked against the version's allowlist and tenant policy. A refusal is a policy decision recorded in the audit trail. |
| Metering | Tokens and requests are attributed to the run, so spend rolls up to sessions, agents, and principals. |
| Tracing | Each call becomes a span in the run's execution trace, on the tenant's own trace plane. |
| Isolation | The gateway is per tenant. Traffic from one tenant never shares a gateway with another. |
You do not configure the gateway. It is provisioned with the tenant; the manifest is the only
input you control.
## Choose between models
A single agent can hold a capable default and a faster, cheaper model in its allowlist, and let
the calling application pick per request. Keep the allowlist short: every entry is a model the
agent is authorized to call, and the ceiling entry must cover its family.
`model` and `permitted_models` are part of the version's contract. To add a model, deploy a new
version and roll it out; a running version's list never changes.
## Where to go next
- [Agents and versions](/concepts/agents-and-versions): what else the version contract carries.
- [Policy, approvals, and evidence](/concepts/governance): where the model decision sits among
the other ceilings.
- [Agent manifest](/reference/agent-manifest#models): the field reference.
---
Source: /guides/build/claude-agent-sdk
# Claude Agent SDK
Write the Claude Agent SDK entrypoint, know what the runtime owns, and wire approvals and questions through the manifest.
With the Claude Agent SDK harness your whole agent is one callable that returns a prompt and the
options Claude should run with. The runtime supplies the model, the workspace, credentials, and
session continuity, and composes the platform's approvals with your own tool restrictions.
- Scaffold with `tanomis agent new NAME --harness claude-agents-sdk`.
- `create_agent(input_payload, context)` returns `(prompt, options)`; options can be a plain
dict, so the module imports without the runtime.
- Never set a model or working directory in the entrypoint; the manifest and runtime own them.
- Keep `permission_mode: "default"` so the platform's approval-required tools can pause the run.
## Scaffold
```bash title="Terminal"
tanomis agent new support-copilot --harness claude-agents-sdk --agents-root ./agents
```
## The entrypoint
```python title="agents/support-copilot/agent.py"
from __future__ import annotations
def create_agent(input_payload, context):
prompt = input_payload.get("prompt", "Triage this request.")
return (
prompt,
{
"system_prompt": (
"You are a support triage agent. Return severity, a short summary, "
"and the next best action."
),
"permission_mode": "default",
"max_turns": 12,
},
)
```
The callable may also accept only `context`, accept no arguments, or be async. A plain options
dictionary keeps your module importable outside the managed runtime, which is what the generated
contract test relies on.
## Who owns what
| Concern | Owner |
|---|---|
| System prompt, allowed and disallowed tools, max turns, hooks, subagents | You |
| Workspace root and current working directory | Runtime |
| Model resolution and provider credentials | Runtime |
| Session ID, resume state, partial-message streaming | Runtime |
| Tool decision callbacks | Composed: your restrictions first, then platform approvals |
| Governed connector configuration | Runtime |
Do not set a model or working directory in the entrypoint to bypass the manifest. The runtime
resolves both for the tenant and version, and a run that tries to override them is refused.
## Approvals and questions
Name the tools that need a person in `agent.yaml`, and let the harness ask questions:
```yaml title="agent.yaml"
policy:
approval_required_tools:
- Bash
enable_ask_user: true
```
Keep `permission_mode: "default"`. `bypassPermissions` skips the SDK callback path and is not
compatible with governed approvals. See [Approval-required actions](/guides/run/approval-required-actions)
for what happens when the run pauses.
## Validate and deploy
```bash title="Terminal"
cd agents/support-copilot
uv run python -m unittest tests/test_agent_contract.py
tanomis agent deploy . --tenant northwind-sandbox --dry-run
```
## Where to go next
- [Write your agent](/guides/build/write-your-agent): the contract every harness shares.
- [Manifest essentials](/guides/build/manifest-essentials): models, tools, hosts, limits.
- [Test before you deploy](/guides/build/test-before-you-deploy): the sandbox tenant.
---
Source: /guides/build/connect-your-models
# Connect your models
Bring your own model endpoints or provider accounts and route agents to them through the tenant gateway.
Agents call models through the tenant's LLM gateway. Registering your own provider accounts or endpoints behind that gateway, so agents use your keys, quotas, and regions, is planned.
This page is planned and not yet written, because the capability it describes is not yet
generally available. The structure is here so you can see where it fits; the notice comes off
when it ships.
## What this page will cover
- Register a provider or endpoint for the organization.
- Expose it to tenants as model IDs agents can declare.
- Failover and aliasing between providers.
- What stays enforced: allowlists, metering, tracing.
## Meanwhile
- [Choose models](/guides/build/choose-models) covers the models available to your tenant today.
---
Source: /guides/build/deep-agents
# Deep Agents
Return a compiled Deep Agents graph from the entrypoint while Tanomis owns the model route, checkpoints, and the workspace.
With the Deep Agents harness your entrypoint returns a compiled LangGraph graph built by
`create_deep_agent`. Tanomis resolves the tenant-approved model before the graph is created,
roots the checkpointer and filesystem backend in the session workspace, and turns the manifest's
approval list into tool interrupts.
- Scaffold with `tanomis agent new NAME --harness deepagents`.
- Pass `context.runtime_context.model` to `create_deep_agent` unchanged; it is already the
resolved, provider-routed model.
- Keep the generated dependencies: Deep Agents, the workspace SQLite checkpointer, and the
provider adapters the managed route needs.
- Tools named in `policy.approval_required_tools` become interrupts; approve, reject, and
answers are supported, edited arguments are not.
## Scaffold
```bash title="Terminal"
tanomis agent new research-assistant --harness deepagents --agents-root ./agents
```
## The entrypoint
```python title="agents/research-assistant/agent.py"
from __future__ import annotations
def create_agent(input_payload, context):
from deepagents import create_deep_agent
def classify_request(text: str) -> str:
"""Classify a request as question, incident, or feedback."""
normalized = text.lower()
if "error" in normalized or "failed" in normalized:
return "incident"
if "?" in text:
return "question"
return "feedback"
return create_deep_agent(
model=context.runtime_context.model,
tools=[classify_request],
system_prompt=(
"Classify the request, explain the evidence, and recommend one next action."
),
)
```
Import `deepagents` inside the callable so the module imports without the SDK, which is what the
generated contract test relies on. Pass `context.runtime_context.model` through unchanged.
## Keep the generated dependencies
The scaffold pins Deep Agents, the workspace SQLite checkpointer, and the provider adapters the
managed model route requires. Do not remove an adapter because the manifest's model name looks like
a different provider; the gateway may route it there. Lock before you deploy:
```bash title="Terminal"
cd agents/research-assistant
uv sync --frozen
uv lock --locked
```
## Who owns what
| Concern | Owner |
|---|---|
| Tools, system prompt, middleware, structured output, subagents | You |
| Workspace root and the runtime session or thread ID | Runtime |
| Default checkpointer and persistent store | Runtime |
| Resolved model and provider credentials | Runtime |
| Filesystem backend root and platform deny rules | Runtime |
| Additional in-workspace backend routes | You, within manifest policy |
| Tool interrupts | Composed from the manifest's approvals and your configuration |
The generated manifest uses `workspace-sqlite` checkpointing and the workspace-composite backend.
Keep those defaults for the first deployment.
## Approval-aware tools
```yaml title="agent.yaml"
policy:
approval_required_tools:
- execute
enable_ask_user: true
```
The runtime combines that list with your own `interrupt_on` configuration. The approval surface
supports approve, reject, and user answers; do not design a tool that needs edited arguments as an
approval outcome.
## Where to go next
- [Write your agent](/guides/build/write-your-agent): the contract every harness shares.
- [Manifest essentials](/guides/build/manifest-essentials): models, tools, hosts, limits.
- [Approval-required actions](/guides/run/approval-required-actions): what a paused run looks
like from your application.
---
Source: /guides/build/generative-ui
# Generative UI
Let an agent return structured UI, rendered by the Console or your own application, instead of only text and tool calls.
Today an agent's output is text, tool calls, and files. Generative UI lets an agent emit typed UI elements, such as forms, tables, choices, and progress views, that the Console or your own application renders, with the user's response flowing back into the run.
This page is planned and not yet written, because the capability it describes is not yet
generally available. The structure is here so you can see where it fits; the notice comes off
when it ships.
## What this page will cover
- The UI element types an agent can emit and how they appear in the event stream.
- Rendering elements in the Console and in your own application.
- Returning user input to the run and how it relates to approvals.
- What is recorded in evidence when a user interacts with generated UI.
## Meanwhile
- [Stream and replay events](/guides/run/stream-and-replay-events) covers the event stream generated UI would ride on.
- [Approval-required actions](/guides/run/approval-required-actions) is the structured user interaction the platform supports today.
- [Answer approvals in the Console](/guides/run/answer-approvals-in-the-console) shows how the Console renders that interaction.
---
Source: /guides/build/manifest-essentials
# Manifest essentials
Understand the agent.yaml fields that control identity, state, models, resources, and policy.
`agent.yaml` is the deployable contract for one agent. Start with `tanomis agent new`; the generated
manifest carries safe defaults and a minimal model authorization grant.
This page covers the fields you touch first. For every accepted setting — including MCP connectors,
delegation, capability metadata, and the full validation rules — see the
[Agent manifest reference](/reference/agent-manifest).
- `name`, `version`, `framework`, `entrypoint`, `kind`, and `identity_mode` identify the agent
and its behaviour.
- `persistent: true` and `session` limits shape the workspace; `resources` reserve capacity.
- `model` and `permitted_models`, `network.egress`, and `policy` declare what the version may
reach; every one is a ceiling the tenant can narrow.
## Identity and behavior
```yaml
name: support-copilot
version: 0.1.0
framework: claude-agents-sdk
entrypoint: agent:create_agent
kind: interactive
identity_mode: service
```
- `name` is the stable agent identity.
- `version` labels an immutable build.
- `framework` selects the harness adapter.
- `entrypoint` is `python_module:function`.
- `kind` describes interactive or background behavior.
- `identity_mode` declares the intended attribution posture: `service`, `delegated`, or `hybrid`. Hosted deployments
require it explicitly, and the first non-rejected version pins it, so choose with your organization
administrator before production use. Read [Agent identity and access](/concepts/identity-and-access)
before choosing: availability and enforcement differ by path, and a mode grants nothing by itself.
## Persistence and warm starts
```yaml
persistent: true
session:
idle_timeout: 30m
max_lifetime: 24h
```
`persistent` provisions durable tenant-scoped storage and is required for new hosted versions.
Hosted sessions are moving to one managed worker boundary. Within that rollout, a snapshot can make
startup faster, but it does not select a different runtime and is not controlled by the agent
manifest.
Older manifests may still contain `cr_enabled`. The platform accepts it temporarily for internal
rolling-deployment compatibility, but current runtime selection does not consult it. The deploy
pipeline and CLI still use `true` temporarily to request a golden build until their follow-up
cutover. During that rollout, a server may require `cr_enabled: true` on a manifest that declares
egress or settlement; this admission check disappears after universal-worker and golden-policy
cutover.
The harness owns its checkpoint format while the runtime owns the workspace and lifecycle.
## Models
```yaml
model: "your-workspace-model-id"
permitted_models:
- "your-workspace-model-id"
```
`model` is the default resolved model. `permitted_models` is the runtime allowlist for overrides.
Use model IDs available to the selected tenant. When changing the model, update the matching
`llm::invoke` scope emitted by the scaffold.
## Resource requests
```yaml
resources:
cpu: 100m
memory: 512Mi
```
These are scheduling requests, not the burst ceiling. Leave them at the scaffolded values until
measurements show a reason to change them. Optional `cpu_limit` and `memory_limit` raise the
per-agent ceiling when tenant policy allows it.
## Network egress
```yaml
network:
egress:
- github.com
```
The external hostnames this agent may reach. Entries are bare, lowercase hostnames, and an entry
covers its subdomains — `github.com` admits `api.github.com`. Wildcards, addresses, and in-cluster
names are refused at deploy.
The platform's own destinations — the managed model gateway and governed connector routes — are
always available and are never declared here; your list only adds to them. Your administrator can
set a tenant ceiling that narrows what you declare. On the managed worker path, egress is enforced
identically on snapshot restore and clean bootstrap; if that boundary cannot be established, a
session with author-declared egress is refused rather than sent to the transitional ordinary path.
> **Availability:** Author-declared egress is enabled per tenant. See [What is available where](/reference/availability) and the [manifest reference](/reference/agent-manifest#network) for the grammar and enforcement points.
## Approvals and authorization
```yaml
policy:
approval_required_tools:
- Bash
enable_ask_user: true
scope_ceiling:
- "llm:your-model-family:invoke"
allowed_audiences:
- "https://your-workspace-api.example"
```
- `approval_required_tools` pauses listed tools for a human decision.
- `enable_ask_user` exposes the harness question flow.
- `scope_ceiling` declares the maximum set of concrete capabilities a scoped token may carry.
Wildcard grants are not accepted.
- `allowed_audiences` declares HTTPS resource audiences for scoped tokens and is required when a
scope ceiling is present.
> **Availability:** These authorization declarations validate and record the intended contract,
> but scoped service-token acquisition and delegated acquisition are currently held: no supported
> public runtime workflow uses them. Do not infer a token-minting setup from this example. Read
> [Agent identity and access](/concepts/identity-and-access#what-the-service-assertion-is-and-is-not)
> before designing scoped authority.
The first two fields emit runtime interactions, not token-exchange approvals. Once the version is
deployed, follow [Handle approval-required actions](/guides/run/approval-required-actions) to list,
validate, answer, and audit those durable questions and tool decisions through the Platform API.
When a supported integration makes these fields actionable, use only the audience supplied for
your tenant/environment. Never copy one from another environment.
## Harness settings
Keep only the block matching `framework`:
```yaml
harness:
claude-agents-sdk: {}
```
or:
```yaml
harness:
deepagents:
is_async: true
stream_modes: [messages, updates, custom]
checkpointer: workspace-sqlite
backend: workspace-composite
recursion_limit: 50
```
Validate the whole contract without uploading:
```bash
tanomis agent deploy . --tenant northwind-sandbox --dry-run
```
## Connect tools across workspaces
Declare `integrations` with the published definition and contract, `identity: user` or `workspace`,
and `tools.allow`. Required integrations block execution until configured; use `required: false`
only when the agent can work without those tools. Customer connection IDs and credentials do not
belong in the manifest. Configure them in **Agent → Integrations** after publishing the version.
See the [portable MCP fields](/reference/agent-manifest#governed-mcp-connectors) and
[workspace setup guide](/guides/connect-tools/mcp-connectors).
## Where to go next
- [Agent manifest](/reference/agent-manifest): every field, default, and validation rule.
- [Choose models](/guides/build/choose-models): the model allowlist and the gateway.
- [Test before you deploy](/guides/build/test-before-you-deploy): the dry run and the sandbox.
---
Source: /guides/build/secrets-and-configuration
# Secrets and configuration
Why an agent bundle never carries a credential, how the agent reaches external systems anyway, and where per-run configuration comes from.
An agent does not hold secrets. Model access, tool access, and identity are supplied by the
platform at run time, scoped to the tenant and the version, and recorded. What you ship is
behaviour and a contract; what the platform adds is authority.
- The deploy preflight excludes secret-looking files from the bundle and warns about them.
- Models are reached through the tenant's gateway with the platform's provider credentials.
- External tools are reached through governed connectors, whose credentials the platform holds.
- Per-run configuration comes from the run's `input` and the session's `agent_inputs`, both JSON
your application controls.
## Nothing secret in the bundle
`tanomis agent deploy` flags `.env`, `*.pem`, `*.key`, and any file whose name contains
`credentials`, and excludes them from the package. The manifest never carries a secret either:
`mcp.connectors` references a connector by id, and the connector owns any credential reference.
If a credential was committed by mistake, rotate it; excluding it from the next bundle does not
undo the commit.
## How the agent reaches things
| Need | How it is supplied |
|---|---|
| A model | The tenant's LLM gateway, with the platform's provider credentials. You declare which models; you never see a provider key. |
| An external tool | A governed MCP connector. The connector's service credential, or a member's private connection, is presented at the gateway per call. |
| Another agent | A delegation, authorized per call by the control plane. |
| The platform's own APIs | A per-run identity assertion custodied outside the agent process. It authenticates the run to the platform; it is not an API key you can copy. |
An agent that needs an API the platform does not front should get it through a connector, so the
call is allowlisted, metered, and recorded, rather than by embedding a key.
## Configuration per run and per session
Your application passes configuration as data:
```json title="POST /v1/sessions"
{ "agent_name": "support-copilot", "agent_inputs": { "locale": "en-GB", "tone": "formal" } }
```
```json title="POST /v1/sessions/{session_id}/runs"
{ "input": { "prompt": "Draft a reply.", "case_id": "1842" } }
```
`agent_inputs` is session-scoped initialisation data; `input` is per run. Both reach your
entrypoint. Treat them as untrusted input and validate them; they are not a place for secrets
either, because they are stored with the session and visible in evidence at the tenant's capture
level.
## Environment-specific values
Do not branch on environment in code. The same version deploys to development and production
tenants; what differs is the tenant's policy, ceilings, and connectors. If behaviour must differ,
pass it through `agent_inputs` or ship a separate version.
## Where to go next
- [MCP connectors](/guides/connect-tools/mcp-connectors): how tool credentials are owned.
- [Choose models](/guides/build/choose-models): the gateway.
- [Identity and access](/concepts/identity-and-access): the run identity the platform supplies.
---
Source: /guides/build/session-workspace-and-state
# Session workspace and state
What the agent can write to, what persists across runs and sessions, and how idle and lifetime limits shape a session.
Each session has its own workspace directory. Runs in the session read and write it; a new
session starts clean. Files you attach land there, and the agent's harness state lives there too.
Nothing in the workspace is shared across tenants, and nothing is baked into the version.
- The workspace is `/workspace`, mounted per session over durable tenant-scoped storage.
- State persists across **runs** in a session, and across suspend and resume. A **new session**
starts empty.
- `session.idle_timeout` and `session.max_lifetime` bound how long a session lives.
- Warm starts restore the harness process, not your data: the golden snapshot contains no
tenant state.
## What lives in the workspace
| Content | Written by | Persists |
|---|---|---|
| Files you attach with the Files API | Your application | For the session |
| Files the agent writes | The agent | For the session |
| Harness state: conversation continuity, tool results | The harness | For the session, across runs and suspend/resume |
| A project `CLAUDE.md`, when `enable_claude_md` is on | You, in the bundle | Read as a context file |
| [Agent skills](/guides/build/agent-skills) at `.claude/skills/`, when your agent ships a `skills/` directory | The platform, from your image | Rebuilt from the image each run |
The workspace belongs to one session. A later run continues it; a fork or another session does
not see it. Files are first-class resources with their own ids and integrity checks, so an upload
is never a blind write; see [Attach files to a session](/guides/run/attach-files).
## Declare persistence and limits
```yaml title="agent.yaml"
persistent: true
session:
idle_timeout: 30m
max_lifetime: 24h
```
- `persistent: true` provisions durable tenant-scoped storage and gives each session its own
directory. It is required for new hosted versions.
- `idle_timeout` is how long a session may sit with no run before it is expired.
- `max_lifetime` is the longest a session may exist, idle or not.
Both are compact durations that resolve to whole seconds: `45s`, `30m`, `24h`. An expired session
can be resumed explicitly; see [Sessions and runs](/concepts/sessions-and-runs).
## Idle is cheap
An idle session is suspended: its harness process is checkpointed and the worker released, and it
is restored in well under a second when the next run arrives. The workspace stays where it is. Your
application does not see the difference; the session status stays `idle` either way, and you never
call resume for it.
## What is not state
- **The version's contract** is immutable and lives in the registry, not the workspace.
- **Credentials** are never in the workspace. Connector credentials are held by the platform and
presented at the gateway; see [Secrets and configuration](/guides/build/secrets-and-configuration).
- **Evidence** is recorded outside the session: the audit trail and the execution trace survive
the session's expiry.
## Where to go next
- [Attach files to a session](/guides/run/attach-files)
- [Agents and versions](/concepts/agents-and-versions): warm starts and snapshots.
- [Agent manifest](/reference/agent-manifest#state-warm-starts-and-sessions): the fields.
---
Source: /guides/build/test-before-you-deploy
# Test before you deploy
What you can verify locally, what only a deployed run can show, and how to use a sandbox tenant as your test bed.
There is no local platform mode: the runtime, the gateway, the connectors, and the policy
checkpoints exist only in a tenant. Testing is therefore two layers: what you can check on your
machine in seconds, and what you check with a real run in the sandbox tenant.
- **Locally:** the contract test, your own unit tests, and `tanomis agent deploy --dry-run`.
- **In the sandbox tenant:** `tanomis session run` and runs from your application, with the real
gateway, connectors, and approvals.
- **Before customers:** the evaluation gate and a canary.
## Locally, in seconds
```bash title="Terminal"
cd agents/support-copilot
uv run python -m unittest tests/test_agent_contract.py
tanomis agent deploy . --tenant "$TANOMIS_TENANT" --dry-run
```
The generated contract test imports your module and checks the entrypoint's shape. Add your own
tests beside it for prompt construction, tool functions, and input validation; your entrypoint is
plain Python and imports without the runtime.
The dry run validates the manifest, the lock policy, and the packaged inputs against the tenant's
ceilings, uploads nothing, and ends with "no changes made". Run it on every pull request; see
[Deploy from CI](/guides/deploy/deploy-from-ci).
What a local test cannot show: model behaviour, connector calls, approvals, egress refusals,
session state. Those need a tenant.
## In the sandbox tenant
Deploy to the sandbox tenant, which serves no customer, and run the agent the way your
application will:
```bash title="Terminal"
tanomis agent deploy . --tenant northwind-sandbox
tanomis session run support-copilot --tenant northwind-sandbox \
--prompt "Customer reports that CSV exports stop at 80%. Draft a first response."
```
Then exercise the edges: a prompt that should trigger an approval, a tool call outside the
connector allowlist, a host outside `network.egress`. Each refusal shows up in the run's events
and in the audit trail, which is the behaviour you are testing.
For application-side flows, point your integration at the sandbox tenant's workspace and
follow [Invoke from your application](/guides/run/invoke-from-your-application); the API is the
same in every tenant.
## Before customers
Score the candidate against the version you trust with [Evaluate a version](/guides/evaluate/evaluate-a-version),
then canary it for one or two customers with [Roll out and roll back](/guides/deploy/rollout-and-rollback).
A version that passed in the sandbox is the same immutable build that reaches customers; only
each tenant's policy differs.
## Where to go next
- [Environments and contexts](/guides/deploy/environments-and-contexts): the sandbox beside your
customers.
- [Session workspace and state](/guides/build/session-workspace-and-state)
---
Source: /guides/build/write-your-agent
# Write your agent
The entrypoint contract every harness shares, what the runtime passes in, what you return, and what stays out of your code.
An agent on Tanomis is one Python module with one callable. The runtime calls it with the run's
input and a context, and you return what the harness needs to start. Everything else, the model
route, the workspace, credentials, session continuity, governed tools, is supplied by the runtime.
- The entrypoint is `module:callable`, declared in `agent.yaml` as `entrypoint`.
- It receives `input_payload` (the run's `input` JSON) and `context` (the runtime context).
- With Claude Agent SDK it returns `(prompt, options)`; with Deep Agents it returns a compiled
graph.
- It must not choose a model, a working directory, or credentials. Those are the manifest's and
the runtime's.
## The entrypoint
```yaml title="agent.yaml"
entrypoint: agent:create_agent
```
```python title="agent.py"
from __future__ import annotations
def create_agent(input_payload, context):
...
```
The callable may accept both arguments, only `context`, or none, and it may be `async`. Keep the
module importable outside the managed runtime so the contract test can import it.
## What comes in
| Argument | What it holds |
|---|---|
| `input_payload` | The run's `input` object exactly as your application sent it, for example `{"prompt": "..."}`. Its shape is yours to define; document it for the callers of your agent. |
| `context` | The runtime context: the resolved model under `context.runtime_context.model`, the session, and the workspace. Pass the model through unchanged; the runtime resolved it for the tenant and version. |
## What you return
| Harness | Return | Notes |
|---|---|---|
| `claude-agents-sdk` | `(prompt, options)` | `options` is a plain dictionary: system prompt, allowed and disallowed tools, max turns, hooks, subagents, permission mode. |
| `deepagents` | The compiled graph from `create_deep_agent` | Tools, middleware, and graph state are yours; the model comes from `context.runtime_context.model`. |
See [Claude Agent SDK](/guides/build/claude-agent-sdk) and [Deep Agents](/guides/build/deep-agents)
for a complete example of each.
## Who owns what
| Concern | Owner |
|---|---|
| Behaviour: prompts, tools, turns, hooks, subagents, graph | You |
| Model resolution and provider credentials | Runtime, from `model` and `permitted_models` |
| Workspace root and working directory | Runtime |
| Session identity, resume, and streaming | Runtime |
| Tool decisions | Composed: your restrictions first, then platform approvals |
| Governed connector configuration | Runtime, from `mcp.connectors` |
Setting a model or a working directory in code to bypass the manifest does not work: the runtime
resolves those for the tenant and version, and the manifest is the contract that was reviewed.
## Ask for a person when it matters
Two manifest switches turn moments in your agent into durable interactions: list tools in
`policy.approval_required_tools` to pause for a permission decision, and set
`policy.enable_ask_user: true` to let the agent ask a question. Your code does not implement the
pause; the harness emits it and the runtime holds it. See
[Approval-required actions](/guides/run/approval-required-actions).
## Where to go next
- [Manifest essentials](/guides/build/manifest-essentials): the fields you touch first.
- [Session workspace and state](/guides/build/session-workspace-and-state): what persists between
runs.
- [Test before you deploy](/guides/build/test-before-you-deploy): the contract test and the dry
run.
---
Source: /guides/connect-tools/create-a-custom-connector
# Create a custom connector
Register an MCP server as a governed connector for your tenant, discover and review its tools, activate it, and configure agent access.
A connector is how an agent reaches an MCP server without launching one itself. A tenant
administrator registers the server once; the platform discovers its tools, you review them, and
agents declare the tools they need by referencing this connector in their manifest.
> **Availability:** Custom connectors are enabled per tenant and need the `manage` capability in your workspace. See [What is available where](/reference/availability).
1. **Create** the connector with its endpoint and authentication mode.
2. **Test** reachability and **discover** its tool inventory.
3. **Review** each tool you intend to expose.
4. **Activate** the connector.
5. **Configure agent access** using a direct connector declaration.
## 1. Create
From the Console, open Connectors and choose Add connector. Where the updated ownership controls
are available, choose **Workspace** or **Platform-wide** explicitly. Workspace ownership belongs
to the selected workspace; platform-wide ownership requires platform administration authority and the **Platform** context.
Ownership describes who manages the connector, not which upstream data a user may access.
From the CLI, inspect the supported scope and tenant flags:
```bash title="Terminal"
tanomis mcp connector create --help
```
A connector has a name, the server endpoint, and an authentication mode: service authentication
with a credential reference the connector owns, or none for an unauthenticated endpoint. Secret
material is never placed in an agent manifest.
## 2. Test and discover
```bash title="Terminal"
tanomis mcp connector test CONNECTOR_ID
tanomis mcp connector discover CONNECTOR_ID
tanomis mcp connector tools CONNECTOR_ID
```
`test` probes reachability and `tools/list`. `discover` records the tool inventory and prints the
diff since the last discovery, so a server that changed a tool's schema shows up before an agent
trips over it. `tools` lists the discovered inventory with the names you will use in allowlists.
## 3. Review
```bash title="Terminal"
tanomis mcp connector review CONNECTOR_ID --tool create_issue --status approved
```
A tool's review status is what makes it available to agents. Review is per tool, so a server
with thirty tools exposes only the ones you approved.
## 4. Activate
```bash title="Terminal"
tanomis mcp connector set-status CONNECTOR_ID active
```
Activation is an explicit step; probing and discovery never change status. The server enforces the
transition rules and the authorization; the CLI only refuses values outside the vocabulary.
## 5. Make the connection available to an agent
Reference the connector directly in `agent.yaml`:
```yaml
integrations:
support:
connector: tenant/support-desk
tools:
allow: [search_tickets]
```
Use the actual connector name and approved tools. Run `tanomis mcp connector tools CONNECTOR_ID`
to inspect them. Deploy the agent; there is no catalog association or connection-selection step.
See [the manifest reference](/reference/agent-manifest#governed-mcp-connectors).
## Where to go next
- [MCP connectors](/guides/connect-tools/mcp-connectors): workspace setup and runtime authorization.
- [Tool approvals and schema pins](/guides/connect-tools/tool-approvals-and-schema-pins): reviewed tool definitions.
- [`tanomis mcp connector`](/cli/mcp/connector): endpoint administration commands.
---
Source: /guides/connect-tools/github-repositories
# GitHub repositories
Connect GitHub to a workspace, set which repositories agents may use, and declare repository access in a manifest.
Agents work on GitHub with `git` and `gh`, using a token the platform issues for each run. Three
things decide what a run gets:
1. **The connection.** A workspace admin connects a GitHub organisation or account by installing
the platform's GitHub App on it.
2. **The ceiling.** The workspace admin says which repositories agents may be given, and the most
they may do there.
3. **The manifest.** The agent names the repositories it needs under `access.github`.
A run gets exactly the repositories its manifest names, limited by the ceiling and by what the
installation can see. Nothing is granted per person or per agent outside the manifest.
## Connect GitHub
In the Console, open **Settings → Organization → GitHub** and choose **Connect GitHub**. GitHub asks you to pick
the organisation or account and the repositories the App may see, then asks you to sign in once.
The sign-in confirms you own that account (an organisation owner, or the user it belongs to); the
platform keeps no copy of your GitHub token.
From the CLI, `tanomis github connect` prints the same link.
The first connection allows agents to **read** every repository the installation can see. Adding
or removing repositories on GitHub updates the connection automatically.
## Set the ceiling
The ceiling lists repositories, or `owner/*` patterns, with the most any agent may be given. A
specific repository overrides its owner's pattern.
```bash
tanomis github ceiling set 'anyfind/*' --can read
tanomis github ceiling set anyfind/agent-platform-website-tanomis --can read,push,pull_request
tanomis github status
```
## Declare access in the agent
```yaml title="agent.yaml"
access:
github:
repos:
anyfind/agent-platform-website-tanomis:
can: [read, push, pull_request]
```
Deploy as usual. A declaration the workspace cannot honour is refused with the reason, for example:
```text
access.github: cannot be used in workspace acme: Asks for push and pull_request on
anyfind/agent-platform-website-tanomis; this workspace allows read (anyfind/*). Raise the
ceiling in Settings → Organization → GitHub, or narrow the manifest.
```
`tanomis agent access NAME` shows what an agent may reach in the current workspace, using the same
status the Console shows. See the [manifest reference](/reference/agent-manifest#github-repositories)
for capabilities and slots for global agents.
## What a run sees
Inside the sandbox, `git` and `gh` authenticate automatically for the declared repositories only.
With more than one repository, `gh` outside a clone needs `-R owner/name`. Every `git` or `gh`
operation asks for a fresh token, so lowering the ceiling or disconnecting GitHub stops the next
operation. Tokens expire within an hour and are revoked when the run ends.
---
Source: /guides/connect-tools/integration-contracts
# Declare an integration
Reference a connector and its allowed tools directly in agent.yaml.
An agent references a configured connector directly. No catalog association, contract publication,
or connection-selection step is needed.
```yaml title="agent.yaml"
integrations:
finance:
connector: global/alphavantage-workspace
required: true
tools:
allow: [SYMBOL_SEARCH, COMPANY_OVERVIEW, EARNINGS]
```
Find the connector and its approved tools:
```bash
tanomis mcp connector list
tanomis mcp connector tools CONNECTOR_ID
```
Use `global/NAME` for a platform connector, `tenant/NAME` for a connector owned by the
agent's workspace, or an exact connector ID. An unqualified name works when unambiguous.
A global agent can reference global connectors; publish a tenant-scoped agent when it needs a
tenant-owned connector. The integration map key is just the agent's local name.
The connector supplies the endpoint, credentials and shared/personal account mode. Personal
connectors still require each caller to connect their own account. Credentials never belong in
the manifest. An optional `identity: workspace` or `identity: user` can assert the expected mode;
it must match the connector.
`tools.allow` is the maximum set the agent may use. Optional `tools.approval_required` lists tools
that require confirmation for each call and must be a subset of `tools.allow`. The connector's
current permitted tools and account access can narrow this set, never expand it.
At registration, the platform records the connector ID and approved tool schemas automatically.
These platform-owned details appear separately from author configuration in the Manifest tab.
Deleting and recreating a connector with the same name does not redirect an existing version.
## Changes and availability
- Changing the connector or allowed tools requires a new agent version.
- Rotating credentials on the same connector does not require a new version.
- Changed tool schemas require review and a new agent version.
- **Agent → Integrations** shows the declared connector, readiness and expandable allowed tools.
Operators can disable or enable access for the agent; they cannot substitute another connector.
- A required connector that is unavailable blocks execution. An unavailable optional connector is omitted.
- Disabled access remains disabled when publishing another version or migrating an old declaration.
Existing catalog-based agent versions remain compatible. New agents should use the direct
connector declaration above. No attachment creation or approval workflow is involved.
---
Source: /guides/connect-tools/mcp-connectors
# MCP connectors
Reference configured connectors in your manifest, limit tools, and connect personal accounts.
An agent references a configured connector directly. No catalog association, contract publication,
or connection-selection step is needed.
```yaml title="agent.yaml"
integrations:
finance:
connector: global/alphavantage-workspace
required: true
tools:
allow: [SYMBOL_SEARCH, COMPANY_OVERVIEW, EARNINGS]
```
Find the connector and its approved tools:
```bash
tanomis mcp connector list
tanomis mcp connector tools CONNECTOR_ID
```
Use `global/NAME` for a platform connector, `tenant/NAME` for a connector owned by the
agent's workspace, or an exact connector ID. An unqualified name works when unambiguous.
A global agent can reference global connectors; publish a tenant-scoped agent when it needs a
tenant-owned connector. The integration map key is just the agent's local name.
The connector supplies the endpoint, credentials and shared/personal account mode. Personal
connectors still require each caller to connect their own account. Credentials never belong in
the manifest. An optional `identity: workspace` or `identity: user` can assert the expected mode;
it must match the connector.
`tools.allow` is the maximum set the agent may use. Optional `tools.approval_required` lists tools
that require confirmation for each call and must be a subset of `tools.allow`. The connector's
current permitted tools and account access can narrow this set, never expand it.
At registration, the platform records the connector ID and approved tool schemas automatically.
These platform-owned details appear separately from author configuration in the Manifest tab.
Deleting and recreating a connector with the same name does not redirect an existing version.
The gateway checks current access on every tool call. Disabling an agent integration or revoking
a personal credential blocks subsequent calls without waiting for token expiry.
## Changes and availability
- Changing the connector or allowed tools requires a new agent version.
- Rotating credentials on the same connector does not require a new version.
- Changed tool schemas require review and a new agent version.
- **Agent → Integrations** shows the declared connector, readiness and expandable allowed tools.
Operators can disable or enable access for the agent; they cannot substitute another connector.
- A required connector that is unavailable blocks execution. An unavailable optional connector is omitted.
- Disabled access remains disabled when publishing another version or migrating an old declaration.
Existing catalog-based agent versions remain compatible. New agents should use the direct
connector declaration above. No attachment creation or approval workflow is involved.
## See which agents use a connector
Open **Connectors → a connector → Used by**. Each agent version appears once with its declared tools.
## Read the four independent gates
| Gate | What must be true |
|---|---|
| Manifest declaration | The version names this connector and the requested tool. |
| Connection readiness | The connector is active, approved tool schemas match, and any personal account is ready. |
| Agent access | Access is enabled for this agent and the connector permits the declared tools. |
| Current call authorization | The caller, active run and any required human confirmation permit this exact call. |
Workspace identity: administrator → workspace connection → connector-owned service authority
(credential reference when required; none for an unauthenticated endpoint) → manifest-limited
tool access → external system. Personal identity: member → own account on the declared connector →
that member’s private credential → manifest-limited tool access → external system.
A member cannot register a custom endpoint through personal sign-in. Platform administrators
manage platform connectors; authorized workspace administrators manage connectors in their workspace.
Neither registering an endpoint nor connecting a personal account overrides the manifest's tool
limits, connector restrictions, an agent disable, or required call approval.
## Resume after connecting an account
If account setup interrupts a request, Console retains the prompt and attachments for up to 24 hours.
Connect your account, return to the saved request, and click **Continue**. Returning from OAuth or
refreshing status does not execute the request automatically. **Discard request** removes it.
---
Source: /guides/connect-tools/private-connections
# Private connections and OAuth
Connect your own account, recover a saved request, and disconnect access.
A personal integration uses your account at the external service. The agent manifest selects
the connector and allowed tools; you authorize your own account separately.
Your personal credential is not a shared workspace credential and is not put in `agent.yaml`.
> **Availability:** This guide applies to supported personal connectors where
> integrations are enabled. See [Availability](/reference/availability).
## Connect your account
1. Select the workspace where you will use the agent.
2. Open **Agents → your agent → Integrations** and select the intended version.
3. If the declared connector is disabled or unavailable, ask an administrator to repair it.
Signing in does not override connector restrictions or an agent disable.
4. Choose **Connect your account**. Console opens the configured connection; choose **Connect**
and complete the provider's OAuth flow. An existing provider authorization may be reused.
5. Check the returned account label. Wait for connection preparation to finish if it is still in
progress, then return to the agent or saved request.
The provider decides which consent or installation steps are required. GitHub sign-in alone is
not proof that its App can access a particular repository. This MCP guide does not configure native
Git clone, push, or PR creation.
## Continue a saved request
When a required personal integration is unavailable, Console saves your exact prompt and attachment
references instead of running the agent. **Continue** stays disabled until setup is ready.
Returning from OAuth does not start a run. Return to the same request and click **Continue** once.
Concurrent retries do not create additional executions of that saved request.
Saved requests expire after 24 hours. **Discard request** deletes the saved request without running
it; it does not delete the separately uploaded files. Cancelling OAuth leaves the request blocked.
Use the same signed-in user and workspace to return to your saved request.
## Reconnect or check preparation
If your account needs authorization again, use the connection's Connect action and complete the
provider flow. If Console says your account is connected but the connection is still being prepared,
use **Check setup** or refresh status. Repeated OAuth consent is not a remedy for route preparation.
A missing or disabled workspace grant needs an administrator, not another personal sign-in.
## Disconnect
Open the connection in **Connectors**, choose **Disconnect** for your account, and confirm.
This revokes your platform connection; it does not delete the shared connector or another user's
account. Subsequent portable tool calls using that credential are denied. An operation already
accepted by the provider is not undone. A new request requiring this account stays blocked until
you reconnect and explicitly continue.
Administrators can separately remove the agent's access in **Agent → Integrations**. A successful
personal connection does not override that decision. Personal-account sessions use the verified
run initiator's account and require that person to own the session; they do not borrow another
member's credential.
---
Source: /guides/connect-tools/tool-approvals-and-schema-pins
# Tool approvals and schema pins
Choose approved connector tools and require confirmation for individual calls.
Connector access combines the **reviewed tool inventory**, the **agent's declared tools**, and
**current permission to use the declared connector**. None of these alone authorizes a call.
Read [Declare an integration](/guides/connect-tools/integration-contracts) for the simple
authoring workflow. Tool review approves definitions; confirmation approves an
individual call. The declared connector and any required personal account must be ready for execution.
## Declare the allowed tools
Use an existing connector and its approved tools. Find them with `tanomis mcp connector list` and `tanomis mcp connector tools CONNECTOR_ID`.
```yaml title="agent.yaml"
integrations:
issues:
connector: tenant/issue-tracker
required: true
tools:
allow: [search_issues, create_issue]
approval_required: [create_issue]
```
`tools.allow` is non-empty and closed by default. Every tool must be approved on the referenced connector.
`tools.approval_required` is a subset of that allowlist. Updating a connector cannot
add tools beyond the agent's declaration. Neither an MCP scope string nor an egress allowance
substitutes for this access. See [Agent manifest](/reference/agent-manifest#governed-mcp-connectors).
## Require a person
Each approval-required call creates a permission request for the verified human run initiator.
Review the tool and its arguments before answering. The approval is bound to that run and exact
call and can be consumed only once. A repeated identical call needs another approval. If the
provider fails after consuming an approval, retrying also requires a new approval.
The agent cannot answer its own request. Session ownership, the active run, and current grant are
still checked. See [Approval-required actions](/guides/run/approval-required-actions).
## Use reviewed schemas
The platform automatically snapshots the requested tool descriptions and input schemas, and
output schemas when present, during registration. Authors do not write hashes or contract
references. Runtime compares served tool definitions against that version's snapshot; changed
or unapproved tools are not silently accepted.
After a provider schema change, review the changed inventory and deploy a new agent version to
capture the updated definitions. The new version still declares its connector explicitly, and existing access disables remain in place.
Discovery success alone does not approve inventory or grant agent access.
---
Source: /guides/deploy/automation-credentials
# Automation credentials
Create an organization-owned machine client for CI or a backend, choose its permissions, and rotate its secret.
A pipeline or backend uses an **automation client** (also called an M2M client) owned by your
organization. An owner or admin creates it; the application uses its client ID and secret.
This works the same way on hosted Tanomis and a self-hosted deployment.
- An owner or admin creates the client in the Console or with `tanomis m2m create`; the secret is
shown once.
- A job or service sets `TANOMIS_API_URL`, `TANOMIS_CLIENT_ID`, and `TANOMIS_CLIENT_SECRET`; `tanomis` then runs
unattended, and a backend does the same OAuth exchange itself.
- Profiles: `observe`, `invoke`, `deploy`. A client reaches every tenant in its organization,
including future ones.
- Rotation is additive; disable stops access at once, even for an unexpired token.
## Create a client in the Console
1. Open **Settings → Organization → Machine clients**.
2. Choose **Create client**, give it a name, and select a permission profile.
3. Save the **platform origin**, **client ID**, and **client secret** in your CI or service secret
manager before closing the dialog. The secret is shown once.
For hosted Tanomis the platform origin is `https://api.tanomis.com`. For a self-hosted installation,
use your deployment's public API origin. This is an HTTP address, not an OAuth audience identifier.
You can also create a client while signed in as an organization owner or admin:
```bash
tanomis m2m create production-automation --profile deploy
```
Use `--org ORGANIZATION` if you belong to more than one organization. The output includes
`Client:` (the platform record ID used for management), `Client ID:` (the OAuth credential ID),
`Client secret:`, and the credential ID and expiry. Store the secret securely; do not send this
output to a shared job log.
## Give tanomis three values
Inject these variables from your secret manager. The values below are placeholders:
```bash
export TANOMIS_API_URL=https://api.tanomis.com
export TANOMIS_CLIENT_ID=''
export TANOMIS_CLIENT_SECRET=''
tanomis agent ls --tenant northwind-sandbox
```
No `tanomis login` step is needed. `tanomis` discovers authentication settings from the platform, exchanges
the credentials for an OAuth access token, and keeps the token in memory. It does not save the
machine secret in the human login credential store. For a private certificate authority, configure
`TANOMIS_CA_BUNDLE` with your CA bundle path.
Choose a tenant explicitly for predictable automation. Without one, `tanomis` can select the
organization's sole ready tenant or its ready default. A selected tenant controls where a command
runs; it does not narrow what the credential can reach.
## Choose permissions
| Profile | What it allows |
|---|---|
| `observe` | Read agents, builds, sessions, operations, tenants, workspaces, and identity. No session file content. |
| `invoke` | `observe`, plus run agents, manage session files, and run `tanomis eval ci`. |
| `deploy` | `observe`, plus register, build, deploy, promote, and cancel agent builds. |
`deploy` does not include `invoke` or access to the evaluation API. `invoke` reaches only the
evaluation routes `tanomis eval ci` uses: it reads an agent's checks and datasets, replays a dataset
through a version, and compares the results. It cannot change checks, guards, or datasets. Automated registration and
deployment are tenant-scoped; this credential cannot publish deployment-wide agents. Organization
membership, tenant creation and offboarding, and machine credential management require a signed-in
human owner or admin.
Every client reaches every active tenant with a ready attachment to its organization, including
future tenants. There are no per-tenant grants for machine clients. Use separate organizations
when credentials must be isolated between groups of tenants.
The profiles supply a standard set of scopes. Owners and admins can also select supported scopes
with `tanomis m2m permissions CLIENT --scopes read:agents,read:identity`. Removing a scope or disabling
the client takes effect at the next authorization decision, even for an existing token. Adding
scopes also requires a newly issued token containing them.
## Rotate and revoke
Run lifecycle commands as a human owner or admin. If your shell has machine credentials set,
unset `TANOMIS_CLIENT_ID` and `TANOMIS_CLIENT_SECRET` before using your human login.
Use the platform record ID from `Client:` or `tanomis m2m ls`, not the display name or OAuth client ID.
```bash
tanomis m2m ls
tanomis m2m get "$CLIENT"
tanomis m2m rotate "$CLIENT" --label rotation --expires-in-days 90
```
Rotation adds a credential without invalidating the old one. Update your jobs with the new secret,
verify they work, then revoke the old credential ID shown by `tanomis m2m get`:
```bash
tanomis m2m revoke "$CLIENT" "$OLD_CREDENTIAL"
```
Revocation stops new tokens once credential cleanup completes. Tokens already issued can remain
valid until expiry; disable the client if you need to stop access immediately.
An organization can have 20 non-deleted clients. Each client can have two live, unexpired
credentials. The default credential lifetime is 90 days; the maximum is 365 days. If both slots
are occupied, revoke an unused credential before rotating again.
A lost create or rotate response cannot be replayed to recover the secret. Inspect the client,
revoke the unusable credential, and rotate again. `--request-id` allows safe retries of the same
operation, but a replay never returns the secret a second time.
## Change or stop access
```bash
tanomis m2m rename "$CLIENT" production-deployer
tanomis m2m permissions "$CLIENT" --profile observe
tanomis m2m disable "$CLIENT"
tanomis m2m enable "$CLIENT"
tanomis m2m delete "$CLIENT"
```
Disable is reversible. Delete withdraws access and starts credential cleanup. Neither requires
waiting for existing access tokens to expire. Lifecycle operations are attributed to the human
who performed them, without recording the secret.
Existing `COG_LOGTO_*` CLI configuration remains supported for compatibility. New organization
clients should use the three provider-neutral variables above.
## Where to go next
- [Deploy from CI](/guides/deploy/deploy-from-ci)
- [Authenticate a backend](/guides/run/authenticate-your-backend)
- [Organization management API](/reference/organization-management-api)
- [M2M CLI reference](/cli/m2m)
---
Source: /guides/deploy/deploy-a-version
# Deploy a version
What tanomis agent deploy does between your source tree and an enabled tenant, and how to read what it tells you.
One command takes a local agent directory to a version a tenant can serve. This guide explains
each stage so the output makes sense and failures are quick to place.
```bash title="Terminal"
tanomis agent deploy ./agents/support-copilot --tenant "$TANOMIS_TENANT" --dry-run
tanomis agent deploy ./agents/support-copilot --tenant "$TANOMIS_TENANT"
```
1. **Preflight** validates the manifest and the lock policy locally.
2. **Package** produces a deterministic bundle. `--dry-run` stops here and prints the plan.
3. **Build** runs remotely and streams progress to your terminal.
4. **Register** records the immutable version with its declared contract.
5. **Warm-start snapshot** is built or reused.
6. **Enable** assigns the version to the tenant you named. This is the only step that routes traffic.
## 1. Preflight
Before anything leaves your machine, the CLI:
- parses `agent.yaml` and requires a harness (`framework`);
- rejects a stray `Dockerfile` or an unsupported top-level `build` field, because the platform
owns the image;
- flags files that look like secrets (`.env`, `*.pem`, `*.key`, names containing `credentials`)
and excludes them from the package;
- enforces the Python lock policy: a `uv.lock` or a pinned `requirements.txt`. A bare
`pyproject.toml` is rejected.
Fix preflight failures in the source tree; nothing was uploaded.
## 2. Package
The bundle is deterministic: repackaging the same tree yields the same checksum, which is what lets
a version be reproduced and compared later. `--dry-run` ends here and prints what would be built
and for which tenant. Use it in CI to validate a change without creating a version.
## 3. Build
The bundle is uploaded and built remotely. Progress streams to the terminal. A build failure names
the file and step; the version is not registered.
## 4. Register
The build result is registered as a new version of the agent, with the manifest contract captured
as declared: models, network egress, policy ceiling, connectors, resources, session limits.
Registration also validates the declaration against the tenant's ceilings, for example that every
`network.egress` entry is inside the tenant's egress ceiling. A rejected declaration names the
entry and the ceiling that excluded it.
## 5. Warm-start snapshot
For the new version the platform reuses a compatible golden snapshot when one exists, or builds one.
This happens before the tenant is enabled, so the first session never races it. If a snapshot
cannot be produced, the deploy still succeeds: the version is enabled, sessions start cold until a
snapshot exists, and the output says so.
> **Availability:** `cr_enabled` requests eligibility for a server-managed golden snapshot. The
> server may build one, reuse one, skip it, or not offer the capability. Only the CLI's final golden
> message establishes whether sessions warm-start; unavailable, skipped, or failed goldens leave the
> version deployed with cold starts. Do not retry a successful deploy.
## 6. Enable
The version is enabled for the tenant you passed with `--tenant`. Other tenants are untouched.
Enablement is tenant-scoped and reversible:
```bash title="Terminal"
tanomis agent get support-copilot --tenant "$TANOMIS_TENANT" # what this tenant serves now
tanomis agent versions support-copilot --by-tenant # who serves which version
tanomis agent disable support-copilot --tenants "$TANOMIS_TENANT" # stop serving it here
```
`disable` removes only the tenant's enablement and records an audit event. The agent and its
versions are untouched, and re-running it is safe.
The deploy output ends with the new version label and the tenant it is enabled for.
`tanomis agent versions NAME --by-tenant` lists that tenant under the new version.
## Deploy from CI
Deploys are non-interactive once the CLI is authenticated. A typical pipeline runs a dry run on
every change and a real deploy on merge, then rolls out to customer tenants deliberately with
[Roll out and roll back](/guides/deploy/rollout-and-rollback) rather than deploying straight to
them.
## Where to go next
- [Roll out and roll back](/guides/deploy/rollout-and-rollback): canary a version for named
tenants, graduate it, or return to the known-good version.
- [Agents and versions](/concepts/agents-and-versions): why versions are immutable and what
enablement means.
- [`tanomis agent deploy`](/cli/agent/deploy): every flag.
---
Source: /guides/deploy/deploy-from-ci
# Deploy from CI
Validate an agent locally in CI and deploy it to a selected tenant with an organization-owned credential.
Create an [automation client](/guides/deploy/automation-credentials) with the `deploy` profile.
Store its client ID and secret in your CI secret store, and set the platform API origin as a CI
variable. The job needs no interactive login.
- **Pull request:** `tanomis agent deploy --dry-run` validates the manifest and lock policy and
uploads nothing.
- **Merge:** deploy to the sandbox tenant.
- **Gate:** the evaluation verdict decides whether a rollout job may run.
- **Customers:** `tanomis agent rollout` to named tenants with a canary share, behind an approval.
## Pipeline example
This GitHub Actions example uses a runner labeled `tanomis` with `tanomis` and your agent's build and
test tools installed. Configure that runner for your repository before using the workflow. Run
your agent's own tests in the validation job as well.
```yaml title=".github/workflows/agent.yml"
name: support-copilot
on:
pull_request:
paths: ["agents/support-copilot/**"]
push:
branches: [main]
paths: ["agents/support-copilot/**"]
jobs:
validate:
runs-on: [self-hosted, tanomis]
steps:
- uses: actions/checkout@v4
- run: tanomis agent deploy agents/support-copilot --tenant "$TANOMIS_TENANT" --dry-run
env:
TANOMIS_TENANT: northwind-sandbox
deploy-sandbox:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
needs: validate
runs-on: [self-hosted, tanomis]
environment: sandbox
steps:
- uses: actions/checkout@v4
- run: tanomis agent deploy agents/support-copilot --tenant "$TANOMIS_TENANT"
env:
TANOMIS_API_URL: ${{ vars.TANOMIS_API_URL }}
TANOMIS_CLIENT_ID: ${{ secrets.TANOMIS_CLIENT_ID }}
TANOMIS_CLIENT_SECRET: ${{ secrets.TANOMIS_CLIENT_SECRET }}
TANOMIS_TENANT: northwind-sandbox
```
Set `TANOMIS_API_URL` to `https://api.tanomis.com` for hosted Tanomis, or your self-hosted API origin.
The tenant IDs are examples; substitute the IDs from your organization. Keep deployment
credentials out of pull-request validation jobs, and use a runner policy appropriate for the code
you allow to execute.
To test a version before anyone is served it, deploy it with `--stage`: it is built, registered,
and able to start sessions, and the tenant keeps the version it serves. Then run `tanomis eval ci`
against it; see [Evaluate a version](/guides/evaluate/evaluate-a-version).
`--dry-run` validates the local package and manifest without API calls or uploads. It does not
check live tenant permissions, quotas, or build availability. The authenticated deployment checks
those and builds and enables the version in the selected tenant.
## Deploy to a customer tenant
Use a protected customer deployment job to deploy the reviewed source revision with
`--tenant bluewater-bank`. Configure your CI environment's approval rules before enabling
that job. An organization M2M client registers tenant-owned agents, so deployment to another
tenant creates or updates that tenant's registration. Do not use `--scope global`, `--tenants all`,
or assume a sandbox tenant's version can be promoted into another tenant's registration.
The `deploy` profile cannot invoke agents or use the evaluation API. Keep any evaluation or
runtime acceptance gate in a separately authorized process; use an `invoke` client when the
check needs to run an agent, including `tanomis eval ci`. A deploy credential reaches all ready tenants in its organization,
so a CI environment name alone is not a platform permission boundary.
After deployment, inspect the enabled version in the target tenant:
```bash
tanomis agent versions support-copilot --tenant "$TANOMIS_TENANT" --by-tenant
```
## Where to go next
- [Automation credentials](/guides/deploy/automation-credentials): rotation and immediate disable.
- [`tanomis agent deploy`](/cli/agent/deploy): deployment flags and output.
- [Customer tenants](/guides/manage/tenants-and-environments)
---
Source: /guides/deploy/environments-and-contexts
# Environments and contexts
Keep a sandbox tenant that serves no customer, reach customers through rollouts, and keep one CLI context per Tanomis environment.
On Tanomis your customers are tenants, so "environments" means something specific: a **sandbox
tenant** that serves no customer, where developers and the pipeline test, and the customer tenants
that a version reaches through a rollout. On your machine, a CLI **context** names which Tanomis
environment you talk to and holds its login.
- One **sandbox tenant** for testing. Agents, sessions, files, connectors, and policy do not cross
tenants, so nothing you do there touches a customer.
- Customers are reached by **enabling the same version** for them, a few at a time. Nothing is
rebuilt.
- One **context** per Tanomis environment on your machine, each with its own login and selected
workspace.
## The sandbox tenant and the customer tenants
| Tenant | Who deploys | Typical policy |
|---|---|---|
| `northwind-sandbox` | Developers, from their laptops and pull requests | Wider model allowlist, broad egress ceiling, approvals off for speed |
| `bluewater-bank`, `orion-retail`, … | The pipeline or an owner, through a rollout | Each customer's own ceilings, approvals on consequential tools |
A tenant's ceilings narrow what a version's manifest declared, so the same version can be admitted
in the sandbox and refused for a customer if its egress list exceeds that customer's ceiling. You
find out at deploy time, with the entry and the ceiling named. Ask your platform operator to
provision the sandbox tenant; see [Customer tenants](/guides/manage/tenants-and-environments).
## Contexts on your machine
A context is local configuration: which API environment, which authentication mode, and the login
state for it. Most people have one, `saas`, created by `tanomis login`. Add another only for a
different Tanomis environment, not for a different tenant.
```bash title="Terminal"
tanomis login --console https://api.tanomis.com --name saas
tanomis config current-context
tanomis config use-context staging
tanomis config view
```
Workspace selection is stored per context. Switching context can change the API environment, the
cached login, and the selected workspace at once, so check `tanomis workspace show` before a
consequential command.
## Switch tenants, not contexts
Within one Tanomis environment, moving from the sandbox to a customer is a workspace change:
```bash title="Terminal"
tanomis workspace use northwind --tenant northwind-sandbox
tanomis agent deploy ./agents/support-copilot --tenant northwind-sandbox
tanomis workspace use northwind --tenant bluewater-bank
tanomis agent rollout support-copilot --version 0.4.0 --tenants bluewater-bank --canary-share 10
```
Deploy and session-run commands refuse a `--tenant` that differs from the selected workspace, so a
stale selection fails loudly instead of deploying to the wrong customer.
## From the sandbox to every customer
Because a version is immutable, reaching customers is enablement, not a rebuild:
1. Deploy to the sandbox. Test with `tanomis session run` and from your application.
2. Run the evaluation gate against the version customers are on.
3. Canary for one or two customers, graduate to the rest, and keep the previous version
deprecated for rollback.
See [Roll out and roll back](/guides/deploy/rollout-and-rollback) for the customer step and
[Deploy from CI](/guides/deploy/deploy-from-ci) for the pipeline shape.
## Where to go next
- [Organizations and tenants](/concepts/organizations-and-tenants): how tenants are attached and
granted.
- [CLI configuration](/reference/cli-configuration): the config file, contexts, and environment
variables.
---
Source: /guides/deploy/retire-an-agent
# Retire an agent
Stop serving a version to a tenant, deprecate old versions, and soft-retire an agent while keeping its history.
Retiring is three separate, reversible-in-different-ways decisions: stop a tenant serving a
version, mark a version as no longer a rollout target, and retire the agent registration itself.
None of them deletes evidence.
- `tanomis agent disable --tenants T` removes T's enablement. Sessions can no longer start; history
stays.
- A version that nothing serves can be deprecated; it remains serveable for rollback.
- `tanomis agent rm` soft-retires the agent registration and preserves its audit history.
## 1. Stop serving a tenant
```bash title="Terminal"
tanomis agent versions support-copilot --by-tenant
tanomis agent disable support-copilot --tenants bluewater-bank
```
`disable` removes only the named tenants' enablement and records an audit event. It is idempotent:
a tenant that is not enabled is reported and skipped. The agent and its versions are untouched, so
re-enabling later is a `rollout`. There is no `all`; removing every tenant's enablement must be
explicit.
New sessions for that agent are refused in the tenant. Existing sessions keep their history and can
be read; whether an in-flight run completes depends on the runtime's state at the moment of the
change, so disable between runs where you can.
## 2. Deprecate a version
Once no tenant serves a version and you do not intend to roll it out again, deprecate it. A
deprecated version is not a target for new enablement but stays serveable for tenants already on
it, which is what makes [rollback](/guides/deploy/rollout-and-rollback#6-roll-back-to-the-known-good-version)
possible. Deprecation is a status transition; see [Versions and statuses](/guides/deploy/versions-and-statuses).
## 3. Retire the agent
```bash title="Terminal"
tanomis agent rm support-copilot --scope global
```
`rm` soft-retires exactly one agent registration. The scope selector is mandatory because global
and tenant-scoped agents may share a name; use `--scope tenant --scope-tenant-id TENANT` for a
tenant-scoped agent. History, versions, sessions, and ledger entries are preserved. Permanent
erasure is an administrator operation and is not reversible.
## Clean up locally
```bash title="Terminal"
tanomis agent delete-local support-copilot --agents-root ./agents
```
Removes the scaffolded directory from disk. It does nothing on the platform.
## Where to go next
- [Roll out and roll back](/guides/deploy/rollout-and-rollback): moving tenants between versions
before you retire one.
- [Audit and evidence](/guides/audit/audit-and-evidence): where the disable and retire events
land.
---
Source: /guides/deploy/rollout-and-rollback
# Roll out and roll back
Move a global agent version to customers one tenant at a time with a canary, graduate it, and restore the known-good baseline in one command.
Move a new version of a global agent into service one customer at a time. The loop is always the
same: **inspect → preview → change → verify**. You will canary `support-copilot@v2` for two
customer tenants, graduate it to their baseline, and, if the release must be reversed, repoint both
to the still-serveable known-good `v1`.
- **Inspect** what each tenant serves, **preview** with `--dry-run`, **change** one tenant at a time, **verify** with `versions --by-tenant`.
- A canary adds a version at a share while the baseline keeps serving; graduation makes it the baseline.
- Rolling back is enabling the still-serveable previous version; nothing is rebuilt and versions are never changed in place.
- Command output is provisional. Only the follow-up version view proves what a tenant serves.
Before: `acme-eu` and `acme-us` use baseline `v1` with no canary → Canary: both keep baseline `v1`
and add `v2` at 10% → Graduated: both use baseline `v2`, and unreferenced `v1` may become deprecated
→ Rolled back: both use serveable, deprecated `v1` with no canary. Version build and manifest
contents stay immutable, while lifecycle status and tenant assignments change separately.
## What each command changes
A version's lifecycle status and a tenant's traffic assignment are separate controls. Promotion
decides whether a global version may serve; enablement decides where it serves.
| Command | What it changes | Tenant traffic |
|---|---|---|
| `tanomis agent deploy` | Builds and registers an immutable version and, depending on scope, requests enablement. | Follow the deploy result. |
| `tanomis agent promote` | Moves a global version to `production` after its gates pass. | None. |
| `tanomis agent enable` | Writes a tenant's baseline and optional canary directly. Accepts any serveable version, including deprecated. | The named tenants. |
| `tanomis agent rollout` | Promotes the version if needed, then enables it for the named tenants. | Only the named tenants. |
| `tanomis agent disable` | Removes the named tenants' enablement; the agent and versions stay. | Stops service there. It is a kill switch, not version rollback. |
A **baseline** is the version a tenant normally receives; a **canary** is an optional second
version with a tenant-specific share.
## 1. Check access and current assignments
You need an authenticated operator context authorized for `read:agents`, `promote:agents`, and every
named target tenant, plus an existing global agent, a known-good production version, a candidate
version, and explicit tenant IDs. Scopes alone do not grant platform support access to a customer
workspace: that path also requires a valid, short-lived support assertion bound to the target
customer tenant, obtained through your organization's authorized support workflow. A
support assertion is bound to exactly one target tenant. Platform support acting into
customer workspaces must run one tenant per command under that tenant's assertion; comma-separated
batches are only for an operator whose ordinary authority already covers every named tenant.
```bash title="Terminal"
tanomis auth status
tanomis agent versions support-copilot --by-tenant
```
`tanomis auth status` confirms the context and login; it does not list granted scopes, tenant
grants, or a support assertion, so a permission error can mean any of the three is missing.
Confirm that `v1` is `production`, both tenants list `v1` under **BASELINE TENANTS**, and `v2` is
the candidate. Record `v1`: that is the rollback target. The Registry may deprecate an older production version
once a newer one supersedes it and nothing references it. Built-in `deprecated` is terminal for status
transitions but remains serveable: tenants can keep using it, and rollback can repoint tenants to it
with `agent enable`; `agent rollout` cannot perform that rollback because it first tries to return
every non-production target to `production`. A tenant with no baseline needs one before a canary;
canary mode cannot create a tenant's first assignment.
## 2. Preview one tenant's canary
Start with one tenant and a 10% share. A canary share must be from 1 through 99; omitting
`--canary-share` means a full baseline assignment.
```bash title="Terminal"
tanomis agent rollout support-copilot \
--version v2 \
--tenants acme-eu \
--canary-share 10 \
--dry-run
```
```text
dry-run: rollout support-copilot@v2 (would first promote v2 registered -> production)
TENANT BEFORE (base/canary) AFTER (base/canary)
acme-eu v1 / - v1 / v2 (10%)
no changes made (dry-run)
```
Dry run reads the current state and prints the proposed matrix without
running promotion gates, promoting, or enabling anything. Stop if the tenant, baseline, candidate,
or share differs from your plan. It does not predict application health or move existing sessions.
## 3. Start the canary and verify it
```bash title="Terminal"
tanomis agent rollout support-copilot \
--version v2 \
--tenants acme-eu \
--canary-share 10
tanomis agent versions support-copilot --by-tenant
```
Rollout first ensures `v2` is `production`; if a required gate fails, the command stops before enabling any tenant.
It then uses the `v1` baseline observed for `acme-eu` when the command began and adds `v2` as the
10% canary. Serialize assignment changes for each tenant: canary does not use a compare-and-set precondition, so a
concurrent baseline change after the initial read can be overwritten by the canary's cached baseline.
Expect `acme-eu` beside `v1` in **BASELINE TENANTS** and `acme-eu (10%)` beside `v2` in **CANARY
TENANTS**. Treat canary output and process exit as provisional: a missing baseline is printed as a
skip, but another tenant assignment failure can produce an apparent success line and a zero-failure
summary. Only this follow-up version view proves the assignment changed. If it does not match,
retry only the tenant that did not change.
## 4. Expand to another named tenant
After your own health and outcome checks pass, preview and apply the same candidate and share for
the next tenant, then verify:
```bash title="Terminal"
tanomis agent rollout support-copilot \
--version v2 \
--tenants acme-us \
--canary-share 10 \
--dry-run
tanomis agent rollout support-copilot \
--version v2 \
--tenants acme-us \
--canary-share 10
tanomis agent versions support-copilot --by-tenant
```
The share belongs to each tenant's assignment; it is not a global split across the tenant list.
Keep naming tenants explicitly as the rollout expands.
## 5. Graduate the candidate
When the canary meets your release criteria, preview a baseline rollout for the same tenants
without `--canary-share`. The batch form assumes ordinary operator authority over both tenants;
support act-as previews and applies each tenant separately.
```bash title="Terminal"
tanomis agent rollout support-copilot \
--version v2 \
--tenants acme-eu,acme-us \
--dry-run
```
The **AFTER** cells must read `v2 / -`. Then apply and verify:
```bash title="Terminal"
tanomis agent rollout support-copilot \
--version v2 \
--tenants acme-eu,acme-us
tanomis agent versions support-copilot --by-tenant
```
For each successful tenant, the plain rollout makes `v2` the 100% baseline and clears its canary.
A partially failed baseline rollout returns an error, but still read per-tenant results and the
version view before declaring it complete. If these were the last `v1` assignments, expect `v1` to
become `deprecated`: superseded, not deleted or unservable.
## 6. Roll back to the known-good version
Rollback repoints enablement to the serveable version recorded in step 1; it is not a lifecycle
transition. Re-check that `v1` is `production` or built-in `deprecated`. `agent enable` has no dry-run mode,
so write the preview down first:
```text
TENANT BEFORE (base/canary) AFTER (base/canary)
acme-eu v2 / - v1 / -
acme-us v2 / - v1 / -
```
```bash title="Terminal"
tanomis agent enable support-copilot \
--tenants acme-eu,acme-us \
--baseline v1
tanomis agent versions support-copilot --by-tenant
```
Each successful tenant now serves `v1` with no canary, even while `v1` is `deprecated`; the
candidate remains an immutable version. Like canary mode, `agent enable` prints per-tenant failures
without a failing exit for a partial result, so require a summary with zero failed tenants and
verify both under the `v1` baseline. Do not use `tanomis agent disable` for this: it removes service
rather than returning a tenant to a known-good version.
## Interpret failures before retrying
| Signal | What changed | Next action |
|---|---|---|
| Permission denied | Nothing. The scope, tenant grant, support assertion, or its tenant binding may be missing. | Have an administrator confirm all of them, then retry. |
| Version not found | Nothing. | Re-run the version view and use an exact version label. |
| Required promotion gate failed | The candidate did not reach `production`; rollout stopped before enablement. | Resolve the gate, then repeat the dry run. |
| Known-good version is built-in `deprecated` | It is serveable, but `rollout` cannot transition it back. | Use named-tenant `agent enable --baseline`. |
| Known-good version is not serveable | The Registry rejects the enablement. | Build the known-good source as a new immutable version. |
| `cannot canary — tenant has no baseline` | That tenant was skipped; canary mode may still exit successfully. | Assign a baseline first, or drop the tenant from the list. |
| Canary output looks successful but the version view disagrees | The tenant did not change. | Retry only that tenant. |
| Another operator changed the baseline during a canary | Canary may have restored the baseline it observed at start. | Serialize assignment changes; repeat the preview. |
| Baseline rollout reports a partial failure | Successful tenants changed; a failed tenant's final assignment may be unchanged or may have committed. | Read the version view before retrying. |
| Rollback enable reports failed tenants | Process exit may still be successful, and a failed tenant's final assignment is unknown until read back. | Read the version view, then re-enable the failed tenants. |
The safe stopping point is always explicit state: the named tenants under the intended baseline
and canary columns in `tanomis agent versions support-copilot --by-tenant`.
## Where to go next
- [Quickstart](/getting-started/quickstart): the first deploy.
- [Deploy from CI](/guides/deploy/deploy-from-ci): the rollout job behind an approval.
- [Journey commands](/cli#roll-out-and-roll-back): compact command lookup.
---
Source: /guides/deploy/versions-and-statuses
# Versions and statuses
Version labels, the status lifecycle from draft to retired, and how status differs from the enablement that routes traffic.
Every deploy creates a version. A version has a label you choose, a status the platform moves it
through, and, separately, the tenants enabled for it. Knowing which of the three you are changing
keeps rollouts predictable.
- **Label** is the `version` in `agent.yaml`, such as `0.4.0` or `v2`. It is yours; it does not
have to be sequential.
- **Status** is the lifecycle: `draft` → `production`, with `rejected`, `deprecated`, and
`retired` as side exits. Status gates whether a tenant may serve the version.
- **Enablement** is which tenants actually serve it. Status alone routes nothing.
## Statuses
| Status | Meaning | Can serve traffic |
|---|---|---|
| `draft` | Registered and built, not yet promoted. The default after `tanomis agent deploy` registers the version, before enablement. | No |
| `production` | Promoted. Tenants may be enabled for it. | Yes, where enabled |
| `rejected` | Reviewed and refused. It never serves. A rejected first version does not pin the agent's identity mode. | No |
| `deprecated` | Still serveable for tenants already on it, so a rollback can return to it, but not a target for new enablement. | Yes, where already enabled |
| `retired` | Withdrawn from service. History is preserved. | No |
`tanomis agent statuses` prints the lifecycle your environment enforces, including the transitions each
status allows and any operator-added custom statuses. Trust it over this table if they differ.
```bash title="Terminal"
tanomis agent statuses
tanomis agent versions support-copilot --tenant "$TANOMIS_TENANT"
```
## Status is not traffic
| Command | Changes status | Changes enablement |
|---|---|---|
| `tanomis agent deploy --tenant T` | Registers a draft, promotes it as part of the deploy | Enables T for the new version |
| `tanomis agent promote --version V` | `draft` → `production` | No |
| `tanomis agent enable --version V --tenants A,B` | No | Enables A and B |
| `tanomis agent rollout --version V --tenants A` | Promotes if needed | Enables A, as baseline or canary |
| `tanomis agent disable --tenants A` | No | Removes A's enablement |
Promotion changes the global status only. If you want a tenant to serve a version, enable it for
that tenant, or use `rollout`, which does both for exactly the tenants you name.
## Labels
The label comes from `version` in the manifest. Use a scheme your team can read in a rollout table:
semantic versions when your agent has an API to other systems, dated labels when it does not. The
platform identifies versions by an immutable id underneath; the label is for people.
Deploying with a label that already exists on the agent registers a new immutable version with the
same label. Avoid it: it makes `versions --by-tenant` ambiguous to read.
## Where to go next
- [Deploy a version](/guides/deploy/deploy-a-version): the stages of a deploy.
- [Roll out and roll back](/guides/deploy/rollout-and-rollback): canary, graduate, return.
- [Retire an agent](/guides/deploy/retire-an-agent): removing enablement and the agent itself.
---
Source: /guides/evaluate/capture-traces
# Capture execution traces
What a run's execution trace contains, the three content capture levels, and who can read a trace.
Every run emits one execution trace on the tenant's own trace plane, separate from the platform's
operational telemetry and separate from the audit ledger. The trace is what evaluators score and
what an engineer opens to see why a run did what it did.
- One trace per run. The session groups the run's turns; the root span is the agent invocation,
with model calls and tool calls as children.
- **Content capture** is a tenant policy with three levels: reference, redacted, full.
- The capture decision is made inside the sandbox, where the content is; the read path rechecks
the tenant's current policy before serving it.
- Traces are tenant-isolated and never copied into the audit ledger.
## What a trace contains
| Span | What it records |
|---|---|
| Agent invocation (root) | The run, its version, the session, timing, and the outcome |
| Model call | The model, token usage, latency, and content at the captured level |
| Tool call | The tool, its arguments and result at the captured level, and the policy decision |
Every span carries the run and session identifiers, so a trace can be matched to the audit ledger
entry for the same run. Spans follow OpenTelemetry's generative-AI conventions, which is what makes
[exporting them to your own backend](/guides/evaluate/export-traces-opentelemetry) a planned
option rather than a rewrite.
## Capture levels
| Level | What is stored |
|---|---|
| `reference` | Structure and hashes only: which calls happened, in what order, with what outcome. No prompt or payload bodies. |
| `redacted` | Bodies with sensitive spans removed by the tenant's redaction rules. |
| `full` | Complete bodies. Serving full content is gated by an approval, not just the policy. |
The level is a tenant setting. An agent cannot raise it: the capture decision is resolved inside
the sandbox from the tenant's policy, and the platform's relay treats span attributes as
pass-through data, so an agent-provided label cannot promote content into a class it is not
allowed to be.
## Who can read a trace
Trace reads are tenant-scoped and re-check the tenant's current policy at read time. The content
class served comes from the server-recognized slot, not from anything the agent wrote. Owners and
admins can see which level their tenant runs in the Agents screen under trace capture.
## Where to go next
- [Evaluation overview](/guides/evaluate/overview): how traces become assessments and verdicts.
- [Audit and evidence](/guides/audit/audit-and-evidence): the ledger that references these
traces by hash.
- [Data handling and retention](/guides/govern/data-handling): planned page on storage and
retention.
---
Source: /guides/evaluate/datasets-and-experiments
# Datasets and experiments
Curate versioned datasets of examples and run experiments that grade a whole dataset and roll up metrics.
A dataset is a versioned set of examples with expected outcomes. An experiment grades a candidate against a dataset version and produces aggregate metrics you can compare side by side.
This page is planned and not yet written, because the capability it describes is not yet
generally available. The structure is here so you can see where it fits; the notice comes off
when it ships.
## What this page will cover
- Create a dataset from production traces or from a file.
- Version a dataset and reference it from a target.
- Run an experiment and read its aggregate metrics.
- Compare experiments side by side.
## Meanwhile
- [Evaluate a version](/guides/evaluate/evaluate-a-version) already covers pinning a target to a dataset version and comparing with a baseline.
---
Source: /guides/evaluate/evaluate-a-version
# Evaluate a version
Test a candidate version against the version serving a tenant on real past runs, and post the result on the pull request.
Before a version serves real traffic, replay real conversations through it and through the version
you already trust, grade both with the agent's checks, and see what got worse. One command does it,
so it drops into the pipeline that builds the agent.
> **Availability:** Evaluation is enabled per tenant. See [What is available where](/reference/availability).
- **Checks:** the agent's checks (Quality > Checks) grade both versions, the same checks that
grade its live runs.
- **Dataset:** runs you picked in the Console with **Add to dataset**. The replay reads each run's
first message again; nothing from the conversation is copied into the dataset.
- **One command:** `tanomis eval ci` replays the dataset through both versions at once, compares
them, and replays what got worse once more before calling the candidate worse.
- **Result:** a Markdown summary for the pull request, and an exit code the pipeline can act on.
## Before you start
- The agent has checks. `tanomis eval ci` grades with them and refuses to run without them.
- A dataset of the agent's runs. On the Quality screen, open a run and choose **Add to dataset**;
collect the conversations you want every new version to keep handling well.
- The candidate version is built and registered in the tenant. Stage it so nobody is served it
yet:
```bash title="Terminal"
tanomis agent deploy agents/support-copilot --stage
```
This stages the version in `agent.yaml`, say `0.4.0`. A staged version can start sessions, so it
can be replayed, but the tenant keeps serving the version it served. Release it within 14 days
with `tanomis agent promote support-copilot --version 0.4.0 --scope-tenant TENANT`; a staged
version nobody releases is then rejected and its storage reclaimed.
## Run it
```bash title="Terminal"
tanomis eval ci support-copilot --dataset refunds --candidate 0.4.0 --summary summary.md
```
The baseline is the version serving the tenant; name another with `--baseline`. Pin a dataset
version with `--dataset refunds@3`, otherwise the latest is used. `--threshold 0.05` lets up to 5%
of the comparable examples get worse before the test fails; the default is none.
The command prints the summary and exits with:
| Exit code | Meaning |
| --- | --- |
| `0` | No worse than the baseline. |
| `1` | Worse: some examples that passed on the baseline fail on the candidate. |
| `2` | Not enough to tell, for example when too few examples could be replayed. |
| `3` | The test could not run (no checks, unknown dataset or version, lost connection). |
An answer of "worse" is checked before it is given. Each example that passed on the baseline and
failed on the candidate is replayed once more on the candidate; it counts only if it fails again.
One that passes on the second try is listed as flaky and not counted, and the verdict is worked
out again from the examples that failed twice. Turn this off with `--confirm=false`.
The summary lists each check's pass rate on both versions, each example that got worse with a link
to its run in the Console, and, separately, the flaky ones. The Console's address is taken from
the API's (`api.example.com` gives `console.example.com`); name it with `--console-url` if yours
differs. When it cannot be worked out, the summary shows trace ids instead.
Each example runs through the production run path on both versions, with the tenant's real
connections: a tool call a replay makes really happens. Test in a tenant whose connections point
at test systems, such as your sandbox tenant, and build the dataset from that tenant's runs.
A replay runs with nobody there. If a version stops to ask a person, for example because a rule
holds a shell command for approval, the replay is stopped at once, nothing is left in your
approvals inbox, and the summary counts that example as one that could not be replayed and says
what it asked for. A run that had attached files cannot be replayed either.
## In a pull request
The job stages the pull request's version in the sandbox tenant that holds the dataset, then tests
it against the version that tenant serves. Staging changes nothing anyone is served; the replays
themselves are real runs (see above).
It uses two [automation clients](/guides/deploy/automation-credentials): a `deploy` client to stage
the version, and an `invoke` client for the test (`invoke` can replay and grade; it cannot deploy).
A deploy client could also release a version, so run the job only for pull requests from branches
of your own repository, in a protected environment. It runs on a GitHub-hosted runner, which
already has `yq` and `gh`; the first step installs the `tanomis` CLI, pinned to a release so a new
CLI version cannot change the job under you. It posts the summary on the pull request:
```yaml title=".github/workflows/agent-eval.yml"
name: support-copilot eval
on:
pull_request:
paths: ["agents/support-copilot/**"]
jobs:
eval:
if: github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
environment: eval
permissions:
pull-requests: write
env:
TANOMIS_API_URL: ${{ vars.TANOMIS_API_URL }}
TANOMIS_TENANT: northwind-sandbox
AGENT: support-copilot
CANDIDATE: 0.0.0-pr${{ github.event.pull_request.number }}.${{ github.run_id }}
steps:
- uses: actions/checkout@v4
- name: Install tanomis
run: |
curl -fsSL https://get.tanomis.com/install.sh | TANOMIS_VERSION=v0.6.0 TANOMIS_INSTALL_DIR="$HOME/.local/bin" sh
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Stage the candidate
run: |
yq -i '.version = env(CANDIDATE)' "agents/$AGENT/agent.yaml"
tanomis agent deploy "agents/$AGENT" --stage --no-stream-logs
env:
TANOMIS_CLIENT_ID: ${{ secrets.TANOMIS_DEPLOY_CLIENT_ID }}
TANOMIS_CLIENT_SECRET: ${{ secrets.TANOMIS_DEPLOY_CLIENT_SECRET }}
- name: Test the candidate
id: eval
run: |
set +e
tanomis eval ci "$AGENT" --dataset refunds --candidate "$CANDIDATE" --summary summary.md
echo "code=$?" >> "$GITHUB_OUTPUT"
env:
TANOMIS_CLIENT_ID: ${{ secrets.TANOMIS_EVAL_CLIENT_ID }}
TANOMIS_CLIENT_SECRET: ${{ secrets.TANOMIS_EVAL_CLIENT_SECRET }}
- name: Comment on the pull request
if: always() && hashFiles('summary.md') != ''
run: gh pr comment "${{ github.event.pull_request.number }}" --body-file summary.md
env:
GH_TOKEN: ${{ github.token }}
- name: Fail unless the candidate passed
if: steps.eval.outputs.code != '0'
run: exit ${{ steps.eval.outputs.code }}
```
The last step fails the check for any exit code other than `0`: worse (`1`), not enough to tell
(`2`), or could not run (`3`). Each run stages a new version under its own label; staged versions
serve nobody, and one not released within 14 days is rejected and its storage reclaimed. The version you release is built from the merged source by your
deploy pipeline (see [Deploy from CI](/guides/deploy/deploy-from-ci)).
The test replays each example on both versions at the same time, so it takes minutes, not seconds.
`--timeout` bounds it (45 minutes by default), and a machine client's token is renewed while it
waits.
The baseline is replayed only when needed. If the same baseline version was graded with the same
checks on the same dataset version within the last 7 days, that result is reused and only the
candidate is replayed; the summary says so and when the baseline was graded. `--baseline-max-age`
changes the 7 days (`--baseline-max-age 0` always replays the baseline), and
`--baseline-experiment` names a finished baseline experiment to reuse; it must have been graded
with the same checks on the same dataset version.
## Step by step
`tanomis eval ci` is built from commands you can also run yourself:
```bash title="Terminal"
tanomis eval target create --agent-version VERSION_ID --dataset-version DATASET_VERSION_ID
tanomis eval target run TARGET_ID --max-examples 50
tanomis eval compare BASELINE_EXPERIMENT --to CANDIDATE_EXPERIMENT --threshold 0.02
```
A target pins one version to one dataset version; running it replays the examples through the
production run path. `tanomis eval compare` exits non-zero unless the verdict is `pass`.
The Evaluations screen in the Console lists each experiment's runs with their results, cost, and
latency, and opens the trace behind each one.
## Where to go next
- [Roll out and roll back](/guides/deploy/rollout-and-rollback): what to do once the candidate
passes.
- [Deploy from CI](/guides/deploy/deploy-from-ci): the deploy half of the pipeline.
---
Source: /guides/evaluate/export-traces-opentelemetry
# Export agent traces with OpenTelemetry
Send each run's execution trace to your own observability backend over OTLP, with the content controls your tenant policy allows.
Every run already produces one OpenTelemetry trace on the tenant's trace plane. Exporting it to your own backend over OTLP is planned.
This page is planned and not yet written, because the capability it describes is not yet
generally available. The structure is here so you can see where it fits; the notice comes off
when it ships.
## What this page will cover
- Configure an OTLP endpoint and credentials for the tenant.
- Which spans and attributes are exported, and how content capture levels apply.
- Correlating exported traces with the audit trail by run and session id.
- Sampling and cost.
## Meanwhile
- [Capture execution traces](/guides/evaluate/capture-traces) explains what a trace contains today.
---
Source: /guides/evaluate/human-feedback
# Human feedback and annotation
Record human judgements beside automated assessments and use them to calibrate scorers.
Automated scores are only as good as their calibration. Human feedback on an assessment is stored beside it and feeds calibration.
This page is planned and not yet written, because the capability it describes is not yet
generally available. The structure is here so you can see where it fits; the notice comes off
when it ships.
## What this page will cover
- Annotate an assessment in the Console.
- Feedback through the API.
- Using feedback to calibrate a judge.
## Meanwhile
- [Evaluate a version](/guides/evaluate/evaluate-a-version) shows the Evaluations screen where assessments appear.
---
Source: /guides/evaluate/online-monitoring
# Online monitoring
Score a sample of production runs continuously and alert on drift.
Online evaluation samples live runs, scores them with the same scorers used offline, and watches the trend so a regression shows up before a customer reports it.
This page is planned and not yet written, because the capability it describes is not yet
generally available. The structure is here so you can see where it fits; the notice comes off
when it ships.
## What this page will cover
- Sampling policy and cost controls.
- Monitors and thresholds.
- Where alerts land.
## Meanwhile
- [Audit and evidence](/guides/audit/audit-and-evidence) explains the execution trace these scores are computed from.
---
Source: /guides/evaluate/overview
# Evaluation overview
How Tanomis turns a run into a trace, a trace into an assessment, assessments into experiments, and a verdict into a release gate.
Evaluation on Tanomis is built on the run you already have. Every run produces an execution trace;
evaluators score traces into assessments; a set of assessments over a dataset is an experiment;
and a comparison between experiments becomes a verdict that can gate a rollout.
> **Availability:** Evaluation is enabled per tenant, shadow-first. See [What is available where](/reference/availability).
- **Trace**: one OpenTelemetry trace per run, on the tenant's own trace plane, with content
captured at the level tenant policy allows.
- **Assessment**: a scorer's verdict on one trace, with a reason and the evidence it used.
- **Experiment**: a candidate version replayed over a dataset version, with aggregate metrics.
- **Gate**: a candidate-versus-baseline comparison whose verdict can block promotion.
## The flow
| Step | What it is | Where you see it |
|---|---|---|
| Run | Any run, from the Console, the CLI, or your application | Sessions |
| Trace | The run's model calls, tool calls, timing, and captured content | Evidence, Evaluations |
| Assessment | A scorer's verdict on a trace | Evaluations |
| Experiment | A candidate replayed over a dataset, with rolled-up metrics | Evaluations |
| Verdict | Candidate compared with baseline against a threshold | `tanomis eval compare`, the promotion gate |
Nothing in this flow copies content into the audit ledger. The ledger records that a gate decision
was made and references the trace content by hash; the trace store holds the content under its
own capture, retention, and access rules. See [Audit and evidence](/guides/audit/audit-and-evidence).
## Offline and online
**Offline** evaluation scores a candidate before it serves traffic: pin a target, replay a dataset,
compare with the baseline. This is the path in [Evaluate a version](/guides/evaluate/evaluate-a-version).
**Online** evaluation samples live runs and scores them continuously so drift shows up early. It is
planned; see [Online monitoring](/guides/evaluate/online-monitoring).
## What decides a score
Scorers run over traces. Deterministic scorers check structure and outcomes; judge-model scorers
ask a model, through the same gateway agents use and under an allowlist your administrator
controls. Human feedback recorded beside an assessment is how a judge is calibrated before a gate
is allowed to enforce.
## Where to go next
- [Capture execution traces](/guides/evaluate/capture-traces): what a trace contains and how
much content it carries.
- [Evaluate a version](/guides/evaluate/evaluate-a-version): the offline path, step by step.
- [Datasets and experiments](/guides/evaluate/datasets-and-experiments) and
[Scorers and judge models](/guides/evaluate/scorers-and-judges): planned pages for the
workbench.
---
Source: /guides/evaluate/scorers-and-judges
# Scorers and judge models
Choose built-in scorers, configure a judge model under the tenant allowlist, and publish a tenant-authored scorer.
A scorer turns a trace into an assessment. Some are deterministic; some use a judge model, which calls through the same gateway as agents and under an allowlist your administrator controls.
This page is planned and not yet written, because the capability it describes is not yet
generally available. The structure is here so you can see where it fits; the notice comes off
when it ships.
## What this page will cover
- The built-in scorer library.
- Configuring a judge model and its allowlist.
- Publishing a tenant-authored scorer without shipping code.
- Calibrating a judge before a gate enforces.
## Meanwhile
- [Evaluation overview](/guides/evaluate/overview) explains where scorers sit in the flow.
---
Source: /guides/govern/data-handling
# Data handling and retention
What Tanomis stores about your sessions, runs, files, and traces, for how long, and who can read it.
Tanomis keeps two records with different retention and access rules, plus session workspaces and files. The exact retention periods and encryption options are being confirmed for publication.
This page is planned and not yet written, because the capability it describes is not yet
generally available. The structure is here so you can see where it fits; the notice comes off
when it ships.
## What this page will cover
- Where prompts, tool payloads, files, and traces are stored.
- Retention periods and deletion.
- Content capture levels and per-tenant keys.
- Regional residency.
## Meanwhile
- [Audit and evidence](/guides/audit/audit-and-evidence) explains the two evidence planes today.
---
Source: /guides/govern/restrict-network-egress
# Restrict network egress
Declare the external hosts an agent may reach, understand the tenant ceiling that bounds it, and read a refusal.
By default an agent can reach the platform's own services and nothing else. To let it call an
external API, declare the host in the manifest. The declaration is part of the version's contract:
validated at deploy, frozen for the version, and enforced on every connection at run time.
```yaml title="agent.yaml"
network:
egress:
- github.com
- api.example.com
```
> **Availability:** Author-declared egress is enabled per tenant; without it the list is validated and recorded but no external route exists. See [What is available where](/reference/availability).
- Entries are bare hostnames. An entry covers its subdomains.
- An administrator's per-tenant ceiling only narrows your list; it never adds to it.
- A declaration outside the ceiling fails the deploy in seconds, naming the entry and the ceiling.
- Platform destinations, such as the LLM gateway, are added for you and are not declarable.
## Allow arbitrary public websites
For an agent that needs to browse websites it cannot enumerate in advance, use:
```yaml title="agent.yaml"
network:
egress_mode: public
```
Omit `egress`, or leave it empty. A non-empty hostname list combined with `public` is rejected.
Without `egress_mode`, the existing hostname-list behavior applies; `allowlist` can also be explicit.
An unset tenant ceiling permits public mode. Any configured hostname ceiling, including an empty
one, rejects it at deploy and session admission. Public-mode `cog agent deploy --dry-run` performs
an authenticated read-only ceiling validation; it creates no build and uploads no source.
Public means any canonical public **hostname through the managed proxy**. Private, metadata,
reserved and platform infrastructure addresses remain blocked, including when public DNS points
to them. It does not enable raw IP destinations, direct sockets, UDP or QUIC. Clients must use the
proxy; transport and port limits still apply. Platform credentials and other policy controls remain
unchanged.
**Availability:** Public mode requires runtime-api, cr-node and cr-helper versions supporting the
versioned egress policy, plus an enabled external route and trusted resolver. An older receiver
refuses activation. Deploy the supporting platform release before deploying a public-mode agent.
The declaration is immutable per agent version. Ceiling tightening blocks new sessions and resumes;
it does not close an active connection. Stop affected sessions for immediate revocation, then start
new sessions under the updated ceiling. Session responses expose `external_egress_policy`, and
connection ledger records identify the mode and ceiling revision in their policy ID.
## Write a valid entry
Each entry is a lowercase hostname with at least two labels, no scheme, port, path, or trailing
dot. Anything else is refused at deploy rather than rewritten, so what you wrote is what is
enforced.
| Not allowed | Why |
|---|---|
| `*.github.com` | No wildcards. `github.com` already covers `api.github.com`. |
| `https://github.com`, `github.com:443`, `github.com/path` | The match is on host alone. |
| `140.82.121.4` | An address has no DNS identity; the allowlist matches the host the agent asks for. |
| loopback or in-cluster names | Names for the sandbox itself or the platform's own network are never declarable. |
| `GitHub.com`, `github.com.` | Not canonical. Write `github.com`. |
Declare the narrowest name that covers what you need. `github.com` admits `api.github.com`; it
does not admit `notgithub.com`.
## The tenant ceiling
Your administrator can set a ceiling for the tenant. Your declaration is intersected with it:
- A ceiling of `github.com` admits a declaration of `api.github.com`, because that is narrower.
- A declaration of `example.com` under a ceiling that does not include it fails the deploy with
the entry and the ceiling named, before any build is created.
- An unconfigured tenant admits any public hostname, so an unset ceiling behaves as though only
your manifest applied.
## What is enforced, and where
- **At deploy**: the grammar and the tenant ceiling, before a build exists.
- **At run time**: the managed proxy admits the platform's destinations plus your declared hosts,
or public hostnames when `egress_mode: public` is selected. Every external destination must
resolve to a checked public address. A refused connection fails at the agent with the host named.
## Change the list
The list is immutable for a version. To add or remove a host, change the manifest and deploy a new
version, then roll it out. See [Deploy a version](/guides/deploy/deploy-a-version).
## Where to go next
- [Policy, approvals, and evidence](/concepts/governance): how egress fits with the other
ceilings.
- [Agent manifest](/reference/agent-manifest#network): the field reference.
---
Source: /guides/govern/set-the-authorization-ceiling
# Set the authorization ceiling
Declare the most authority an agent's own tokens can ever carry, in the three scope grammars the platform accepts, and understand what the ceiling does and does not govern.
`policy.scope_ceiling` is the upper bound on what an agent may do acting as itself. Anything that
mints authority for the agent intersects the request with this ceiling; nothing widens it.
```yaml title="agent.yaml"
policy:
scope_ceiling:
- "llm:your-model-family:invoke"
- "mcp:github:create_issue:write"
- "calendar.read"
allowed_audiences:
- "https://your-workspace-api.example"
```
- Three grammars, no wildcards: `.`, `mcp:::`,
`llm::`.
- Required on a hosted control plane. Where it is optional, an absent ceiling means allow-all with
a warning.
- It governs **delegation** between agents and any future scoped token exchange. It does not
govern the model allowlist or connector tool allowlists; those have their own controls.
- Changing it is a new version. The broker reads the registered contract with a short cache, on a
separate clock from traffic rollout.
## Write the entries
| Form | Example | Covers |
|---|---|---|
| `.` | `calendar.read` | A capability on a named resource |
| `mcp:::` | `mcp:github:create_issue:write` | One tool on one connector, with a verb |
| `llm::` | `llm:your-model-family:invoke` | Calling a model family |
Every entry must match exactly one grammar. Wildcards are rejected at validation, so you cannot
write `mcp:github:*`. Keep the list to what the agent actually does: a ceiling is the maximum,
and every entry is authority the agent could be granted.
When you change `model`, update the matching `llm::invoke` entry, or the declared ceiling
stops describing the model the agent calls.
## What the ceiling governs
- **Agent-to-agent delegation.** On a delegated call the caller's ceiling is intersected with the
callee's, and only scopes inside both are granted. An agent with no ceiling cannot receive a
delegated grant at all.
- **Token exchange.** If a supported flow exchanges a token against one of the agent's
`allowed_audiences`, a requested scope outside the ceiling is refused. No public runtime
workflow exposes that exchange today; the contract is held.
## What it does not govern
| Concern | Where it is actually enforced |
|---|---|
| Which model a run may call | `permitted_models`, at the LLM gateway |
| Which integration tools an agent may use | `integrations..tools.allow`, at the connector gateway |
| Which actions pause for a person | `policy.approval_required_tools` and connector `approval_required_tools` |
| Which external hosts the agent may reach | `network.egress` and the tenant ceiling, at the egress proxy |
Adding an `mcp:...` scope does not approve a connector tool, and adding an `llm:...` scope does
not replace the model allowlist. Sizing the ceiling for those will not work.
## Change it safely
The ceiling is part of the version's contract, so changing it means a new version. Two clocks
apply: traffic follows enablement, while the authorization contract is read from the agent's
registered manifest through a short in-process cache. A registration can change the contract
before the new version serves traffic, and rolling traffic back does not necessarily restore the
earlier contract. Review authorization impact at registration time and verify the effective
contract after a change.
## Where to go next
- [Identity and access](/concepts/identity-and-access): the identity modes and why authority
only narrows.
- [Delegate work to another agent](/guides/run/delegate-to-another-agent): the ceiling in use.
- [Agent manifest](/reference/agent-manifest#authorization-contract): the field reference.
---
Source: /guides/govern/support-access
# Support access and break-glass
How Tanomis support can act inside your workspace, what a support grant contains, and where you see it.
Tanomis support sometimes needs to act inside a customer workspace: to help during onboarding,
diagnose a stuck provisioning, or reproduce an issue. That access is never a standing platform
credential. It is a short-lived, signed grant scoped to one workspace, with a reason, an expiry, and
an audit binding.
- Support acts through a **support grant**: one workspace, one reason, a short expiry, signed by
the platform.
- The grant is the only way support reaches a customer workspace; a raw platform credential does
not.
- Every action taken under a grant lands in your organization's audit trail, attributed to the
support principal and the grant.
- You can read the trail at any time; ask your administrator to revoke a grant early.
## What a support grant is
A grant names the target workspace, the support principal, the reason given, and when it expires.
The platform signs it, and every service on the path to your data checks it, so the grant cannot
be widened to another tenant or extended by the holder.
Without a grant, the platform's own administrators have no route into a customer workspace's
sessions, files, or evidence.
## What you see
Actions under a grant appear in the audit trail like any other action, with the support principal
as the actor and the grant as the authority. In the Console, the Assurance section and `tanomis org audit`
show them alongside your members' actions.
```bash title="Terminal"
tanomis org audit --limit 200
```
## Asking for help
When you open a support request that needs someone in your workspace, support asks for the
workspace and states the reason; the grant records both. Revocation is immediate and also recorded.
## Where to go next
- [Audit and evidence](/guides/audit/audit-and-evidence): reading the trail.
- [Organizations and tenants](/concepts/organizations-and-tenants): how access is decided for
everyone else.
---
Source: /guides/govern/usage-and-spend
# Usage and spend
How model usage is metered per run, where you read it, and how a session budget bounds what an agent can spend.
Every model call goes through the tenant's LLM gateway, which meters it against the run that made
it. Usage therefore rolls up from runs to sessions, agents, and the principals who started them,
and a session can carry a budget that bounds it.
> **Availability:** Metering and budgets are enabled per tenant; a session shows usage as not measured where they are off. See [What is available where](/reference/availability).
- Usage is recorded per run: input tokens, output tokens, and the model.
- The Console's session view shows usage by run and, where a budget governs the session, the
cap, what is reserved, what has settled, and what remains.
- A run that would exceed the session's budget is refused before it starts, not cut off midway.
## Read usage
Open a session in the Console and choose the tokens panel. It shows totals for the session and a
breakdown by run, split into input and output. The same numbers are attributed in the execution
trace's model-call spans, so an evaluation experiment can report cost beside quality.
## Budgets
Where a session budget is set, the panel shows:
| Figure | Meaning |
|---|---|
| Cap | The session's budget |
| Reserved | Usage held for a run that is still executing |
| Settled | Usage recorded for completed runs |
| Available | Cap minus reserved minus settled |
A run is admitted only if its reservation fits. "No budget governs this session" means the
session has no cap, and usage is metered but unbounded.
## Where spend is decided
Spend is a consequence of three declarations you already control: the models in
`permitted_models`, the tools the agent may call, and how long sessions live. A cheaper model in
the allowlist that your application selects for routine requests is the largest lever. See
[Choose models](/guides/build/choose-models).
## Where to go next
- [Sessions and runs](/concepts/sessions-and-runs)
- [Evaluate a version](/guides/evaluate/evaluate-a-version): cost beside quality for a
candidate.
---
Source: /guides/govern/write-tenant-policy
# Write tenant policy
Add a rule that limits what agents on a tenant may do, see what it would have done, and publish it as the next version.
A tenant rule is a limit on top of every agent's manifest. The manifest is the ceiling each agent
declared for itself (models, hosts, tools); a rule is the tenant saying "and also not this", for
every agent, for named agents, or for agents in a lifecycle status. Rules live in the Console
under **Policies**.
- A rule is a sentence: **who** it applies to, **what** it watches (model calls, tool calls, or
prompt content, external hosts, or managed shell commands), and whether a match blocks or requires human approval. Hard blocks take precedence.
- The policy is published as **versions**. Editing never touches what is live: edits go into one
pending change, kept as you type, and **Review and publish** makes them the next version, all
together or not at all. **Versions** rolls back and restores.
- Whether a refusal is applied or only recorded is the tenant's posture, the **Observing /
Enforcing** switch at the top of Rules, with the week's number beside it.
- Workspace administrators can publish. Workspace members can read, prepare a change, try it,
and request a publish.
## Add a rule
1. Open **Policies** and choose **New rule**: start from a template (block specific models, block a
tool for some agents, keep secrets out of prompts), write it as code, or upload a policy file.
2. The rule opens on its own screen. Name it: the name is what Activity and the ledger show when it
fires.
3. **Applies to**: all agents, named agents, or agents in a lifecycle status such as
`experimental`. Named agents and a status combine.
4. **Watch**: model calls or tool calls (name the ids, or anything outside the agent's manifest),
or prompt content (terms, patterns, a length cap, the secrets detector).
5. The **Form** and **Code** tabs are two views of the same rule. Anything the form cannot say
stays as code; the list still shows the sentence it can read.
6. **What it would do**, beside the form, replays the last seven days against the rule as you
edit, and **Try a request** judges one request for a named agent (prompt-content rules run the
pasted text through the real inspector).
7. Edits are kept as you type. **Back to rules** never loses work.
### When a prompt-content rule matches
A message someone sends is checked before its run starts. A match refuses the send and names the
rule; nothing reaches the agent, and the session stays usable for the next message. Text the agent
reads on its own, such as tool results, is checked when it goes to the model, and a match ends
that turn with a short refusal in place of the model's reply. Only new text is checked, so a
published rule applies from then on and does not re-judge a session's earlier messages.
## Review and publish
The bar at the bottom of Rules collects your change. **Review and publish** opens a sheet with
every changed rule as a before-and-after sentence, the whole new policy replayed over last week
(and a note when it blocks fewer requests than today's), and a summary line. **Publish** makes it
the next version: every engine source the change touches is validated, approved and activated
together, and if one is refused the ones already switched are put back before you see the error.
If you cannot publish, the same button becomes **Request publish**: the change stays saved with
your name on it and shows at the top of Rules for anyone who can.
## Versions
The live version is named at the top of Rules. **Versions** lists every publish with its author,
summary and what changed, and offers **Roll back** on the live one and **Restore** on older ones.
A restore is a publish of that older policy as a new version, through the same path, so nothing
is edited in place and every step is on the ledger.
## Observing and enforcing
The switch at the top of Rules says whether model-call refusals are applied or only recorded, and
the number beside it says how many the week would have refused. Turning enforcing on is a
platform-operator action; the confirmation names the number and says which planes follow: model
calls enforce, prompt-content and tool-call rules keep observing until the gateway is armed for
them.
## Read what happened
**Activity** lists decisions as what happened: Blocked, Would have blocked, Asked a human,
Allowed, or Could not decide, each naming the rule. **Why?** explains a refusal from the rule, the
reason and the manifest. **Make a rule** opens the rule screen pre-filled from the decision.
## External hosts and managed shell commands
Choose **Control external hosts** or **Control shell commands** from **New rule**. Select
**Block** or **Ask a human**. For approval rules, choose either:
- **Once:** approve this connection or exact command attempt.
- **During this run:** approve repeated connections to the same host and port, or the same
exact command and working directory, while this run remains active. Other command inputs
remain bound too. This never grants unrestricted access for the run.
Host rules accept exact hostnames, `*.example.com` for subdomains (not the base domain),
and an optional port. Command rules name executables such as `rm` or `curl`. Dynamic shell
expressions are treated conservatively as potentially matching command rules.
The operation waits in the **Supervision** inbox until a human approves or refuses it. Unanswered asks
expire after 30 minutes. A network client may time out earlier while waiting; approval
does not restart a request that the client has already abandoned. Approval cannot override a hard block or the agent's baseline
capability limits. Changing or restoring the action policy requires a fresh approval.
**Observing** records the decision without stopping to ask a human. Activity distinguishes
**Asked a human** from **Approved**; approval is not proof that execution succeeded.
**Coverage:** host rules check new external connections at the host proxy. An HTTPS connection
can carry multiple requests; this is not inspection of individual URLs or requests. Command
rules check managed shell tool input before execution. They do not intercept arbitrary
subprocesses or prevent file deletion through Python, scripts, or other programs. Filesystem
policy enforcement is not part of this feature.
Updated host workers and Claude or Deepagents runtimes are required before starting new
runs, including while observing, so switching to enforcement cannot bypass checks.
Enabling external host or command enforcement applies to runs that start after the
switch. Runs that started earlier, including suspended runs that resume later, keep
observing until they end: their matches are recorded as would-deny or would-ask and the
Assurance Ledger entry says the run started before enforcement. The **Policy enforcement**
dialog lists those runs with a **Stop** control for each. An unsupported runtime is refused
rather than silently bypassing an enforcing rule.
These rules use the `cedar / actionfacts/v1` family. Cedar evaluates the conditions;
validated annotations declare the approval requirement and its scope:
```cedar
@id("approve-partner-host")
@effect("require-approval")
@approval_scope("run")
forbid(principal, action == Action::"network.connect", resource)
when { context.target == "api.partner.example" && context.port == 443 };
```
The platform manages the human decision and checks its run, operation, and active policy
binding. A matching unannotated `forbid` remains a hard block.
---
Source: /guides/manage/organization-members
# Organization members and access
Invite people, set roles, grant tenants, transfer ownership, and read the audit trail, from the Console or from tanomis.
An organization's owners and admins manage who belongs to it and which tenants each person can
reach. Everything here is available in the Console under the organization settings and from
`tanomis org`. The server decides every call; the CLI reports refusals as given.
- Roles: **owner**, **admin**, **developer**, **viewer**.
- Owners and admins reach every tenant. Developers and viewers reach only tenants they are
granted.
- Accepting an invitation grants the organization's default tenant automatically.
- Every change lands in the audit trail.
## Invite a member
```bash title="Terminal"
tanomis org invite dana@example.com --role developer
tanomis org invitations ls
tanomis org invitations revoke INVITATION_ID
```
The invitee accepts from the Console link in the email, or with `tanomis org accept --token`. If the
organization has a default tenant, the new member is granted it on acceptance; otherwise grant a
tenant next.
## Grant and revoke tenant access
Developers and viewers see only the tenants named in a grant:
```bash title="Terminal"
tanomis org grants ls
tanomis org grants add PRINCIPAL_ID --tenant northwind-sandbox
tanomis org grants revoke GRANT_ID
```
Capabilities on a grant come from the vocabulary `manage`, `deploy`, `run`, `view`. A grant takes
effect on the member's next request; so does a revocation.
## Change roles and ownership
```bash title="Terminal"
tanomis org members
tanomis org members set-role MEMBER_ID --role admin
tanomis org members remove MEMBER_ID --yes
tanomis org ownership transfer MEMBER_ID
```
Transferring ownership always steps the current owner down; add `--demote-self` to choose the role
you keep. `members remove` asks for `--yes` because it is not reversible from the CLI.
## Read the audit trail
```bash title="Terminal"
tanomis org audit
```
Owners and admins read it. See [Audit and evidence](/guides/audit/audit-and-evidence) for what
the trail contains and proves.
## What members can see
The reads behind these controls are owner-or-admin. A developer or viewer who opens the
organization settings sees an explanation instead of the controls, and `tanomis org` returns the
server's refusal.
## Organization and tenant lifecycle
Creating an organization, attaching or detaching a tenant, setting the default tenant, and
suspending or deleting an organization are platform operations performed with your Tanomis
administrator, not self-service. Ask for them through your support channel.
## Where to go next
- [Organizations and tenants](/concepts/organizations-and-tenants): the model behind roles,
grants, and workspaces.
- [Authenticate and select a workspace](/getting-started/authenticate-and-select-workspace): what
a newly granted member does first.
---
Source: /guides/manage/sso-and-scim
# SSO and SCIM
Sign members in through your identity provider and sync groups automatically.
Members sign in with the hosted identity provider today. Enterprise single sign-on and group synchronisation are planned.
This page is planned and not yet written, because the capability it describes is not yet
generally available. The structure is here so you can see where it fits; the notice comes off
when it ships.
## What this page will cover
- Connect an enterprise identity provider.
- Map identity provider groups to roles and tenant grants.
- Deprovisioning.
## Meanwhile
- [Organization members and access](/guides/manage/organization-members) covers invitations and grants today.
---
Source: /guides/manage/tenants-and-environments
# Customer tenants
Create, inspect, retry, and offboard organization tenants from the Console or CLI.
A tenant is where agents run and their data stays. Your organization can own several customer tenants and a sandbox tenant for testing. Each tenant belongs to one organization.
## Create and inspect tenants
An organization owner or admin can open **Settings → Organization → Tenants** in the Console to
create a tenant and inspect its progress. The equivalent CLI commands use a human login:
```bash
tanomis org tenants create bluewater-bank
tanomis org tenants ls
tanomis org tenants get "$TENANT"
```
Use `--org ORGANIZATION` to choose an organization explicitly. Creation returns before provisioning
finishes. A tenant is usable when its status is `active` and its organization attachment state is
`ready`; inspect both with `tanomis org tenants get "$TENANT" -o json`. Provisioning can take several
minutes. Deployment policy controls the tenant limit and supported provisioning modes.
If provisioning fails, inspect the tenant and retry after the underlying issue is resolved:
```bash
tanomis org tenants retry "$TENANT"
```
## Grant access
Owners and admins see attached tenants. Developers and viewers need a tenant grant; see
[Organization members and access](/guides/manage/organization-members). Use `tanomis workspace ls`
to see the workspaces available to your human login.
Organization-owned [automation clients](/guides/deploy/automation-credentials) automatically reach
all active, ready tenants in the organization, including a newly created tenant as soon as it is
ready. They do not use per-tenant grants. A sandbox CI job's tenant selection is therefore
not a restriction on the underlying credential.
## The default tenant
The default tenant is granted to new members when they accept an invitation. It can also supply
a default selection when a tenant is omitted. An organization with no default auto-grants nothing.
Creating a tenant does not automatically make it the default. Changing the default or attaching
an existing deployment tenant remains an operator operation.
A self-hosted installation starts with a default organization. The operator configures the
installation and its initial access; owners and admins then use the same organization management
flow as hosted customers.
## Sandbox and customer tenants
Grant developers access to the sandbox and only the access they need in customer tenants. Set tenant
policy independently. With a tenant-owned automation deployment, deploy the reviewed source to
each tenant's own agent registration; do not assume a version owned by one tenant belongs to the
other. See [Deploy from CI](/guides/deploy/deploy-from-ci).
## Offboard a tenant
Offboarding immediately withdraws machine access and starts asynchronous resource cleanup. It is
irreversible. Review the tenant and preserve any data you need before confirming in the Console,
or supply its exact ID to the CLI:
```bash
tanomis org tenants offboard "$TENANT" --confirm "$TENANT"
```
Inspect the tenant until cleanup completes. The resources removed depend on the deployment's
provisioning mode. An automation client cannot create, retry, or offboard tenants; these operations
require a human owner or admin.
## Where to go next
- [Organizations and tenants](/concepts/organizations-and-tenants)
- [Tenant CLI reference](/cli/org/tenants)
- [Organization management API](/reference/organization-management-api)
---
Source: /guides/manage/workspaces
# Workspaces
What a workspace is, how to switch between them in the CLI and the Console, and why selecting one never widens what you can do.
A workspace is your view of one organization and one of its tenants: the pair, your role there,
and the capabilities you hold. It is a selection, not a resource. You choose it once per context;
the server re-derives your access from the token, the organization, and the tenant on every
request.
- A workspace is one organization plus one tenant, with your role and capabilities there.
- `tanomis workspace use ORG --tenant TENANT` selects it per context; `show` prints it.
- Selecting supplies context and never widens access; grants are re-checked on every request.
## Switch in the CLI
```bash title="Terminal"
tanomis workspace ls
tanomis workspace use northwind --tenant northwind-sandbox
tanomis workspace show
tanomis workspace clear
```
`use` persists the selection on the active context; `show` prints it beside the role and
capabilities; `clear` removes it. Tenant-scoped commands then send the organization and tenant
selectors for you, and refuse an explicit `--tenant` that disagrees with the selection.
## Switch in the Console
The organization and tenant you are working in appear in the header. Settings shows the
workspaces you can select. Where the switcher is not yet available in your environment, sign in to
the organization you need.
## Switch in your application
Applications do not select a workspace; they send it on every request as the
`X-Organization-ID` and `X-Workspace-Tenant-ID` headers, from the pair returned by
[List workspaces](/api/workspaces/list-workspaces).
## What selecting cannot do
Selecting a workspace supplies context. It cannot attach a tenant, grant one, or reach one you are
not granted; a selection outside your grants fails at the first server-backed call. Grants can
change at any time and take effect on the next request.
## Where to go next
- [Organizations and tenants](/concepts/organizations-and-tenants)
- [Authenticate and select a workspace](/getting-started/authenticate-and-select-workspace)
---
Source: /guides/run/answer-approvals-in-the-console
# Answer approvals in the Console
Where a paused run shows up in the Console, how to answer a permission request or a question, and how the Supervision inbox orders what is waiting.
When an agent reaches an action you marked as approval-required, or asks a question, the run
pauses and the request appears in two places: inline in the session, and in the Supervision inbox
across the tenant. Answering from either resumes the same run.
- **Sessions** shows the request as a card in the run's timeline, with allow and deny, or an
answer box for a question.
- **Supervision** lists every pending request in the tenant in its Inbox, longest-waiting first, with a
"Mine" filter for the sessions you started.
- The first answer wins. A second reviewer sees the recorded decision, not a second prompt.
- A request has an expiry; an expired request ends the run's wait with a refusal, recorded.
- Only a person can answer. A request signed with an agent's delegated credential is refused.
## In the session
Open Sessions, select the session, and find the card at the point where the run paused. A
permission card names the tool and shows its input; a question card shows the question. Choose
Allow or Deny, or type the answer, and the run continues from where it stopped. The card stays in
the timeline as the record of the decision and who made it.
## In the Supervision inbox
The Supervision inbox collects pending requests from every session in the selected tenant, ordered by how long
each has waited, so a reviewer can work through them without opening sessions one by one. Each row
names the agent, the tool or question, who started the session, how long it has waited, and when
it expires. Approve, deny (with an optional reason), or answer directly on the row; "Open session"
takes you to the full timeline.
Two filters shape the list. **Mine** shows requests from sessions you started and is the default
for developers and viewers. **All in tenant** shows every session's requests and is the default
for organization owners and admins. Either way, any member of the tenant may answer any request;
the filter is a view, not a permission.
After you answer, the row reads "Answered, delivering" until the agent has received it, then
"Delivered". A parked session is restored to deliver the answer; viewing the inbox never restores
one. New requests appear within one reconciler sweep, so the header shows when the list was read
rather than claiming to be live. Two identical requests from one session show as one row.
## Rules the Console follows
- A decision is accepted exactly once. If someone else answered first, you see their answer.
- Closing the tab does not lose the request. It is a control-plane record, and the agent's
runtime may be suspended while it waits; it is restored when you answer.
- Denying a permission does not fail the run. The agent learns the tool was refused and decides
what to do next, which may be to finish without it or to ask.
- Everything here is also available to your application through the interactions API, so a
product can present the same decision in its own interface. See
[Approval-required actions](/guides/run/approval-required-actions).
## Where to go next
- [Tour the Console](/getting-started/console-tour)
- [Policy, approvals, and evidence](/concepts/governance): where approvals sit among the other
controls.
---
Source: /guides/run/approval-required-actions
# Approval-required actions
Pause an agent at the actions you choose, show a person exactly one decision, answer once, and follow the same run to completion.
An agent does not have to guess at a consequential action or missing context. It stops at a
durable **interaction**, your application brings a person in, and the same run continues after
the answer. This guide builds that application-side flow against the Platform API: permission
decisions and questions, disconnects, competing reviewers, expiry, and a runtime that suspends
while it waits.
- Declare the moments in `agent.yaml`: `approval_required_tools` for permission decisions, `enable_ask_user` for questions.
- When the run pauses, the session reports `awaiting_input: true`; list its pending interactions and show the person exactly one decision.
- Answer by the stable `interaction_key`. A decision is accepted once; a second answer sees the recorded one.
- Interactions are durable: they survive closed tabs, restarts, and a suspended runtime, and they expire on a deadline you can read.
> **Availability:** Durable approvals and questions are on in hosted Tanomis and operator-enabled when self-hosted. The examples below are the supported wire contract. See [What is available where](/reference/availability).
## Declare the moments
The deployed version opts into the interactions it can emit:
```yaml title="agent.yaml"
policy:
approval_required_tools:
- Bash
enable_ask_user: true
```
`approval_required_tools` makes each listed harness tool wait for a `permission` decision;
`enable_ask_user` lets the harness ask a `question` and wait for a string answer. These are runtime
interactions, separate from `approval_thresholds` in the held scoped-token contract.
Use an explicit session and run so your application holds stable identifiers before an
interaction appears; see [Sessions and runs](/concepts/sessions-and-runs). The API enforces tenant
access but does not assign an interaction to one approver: enforce your own reviewer roles before
exposing Approve or Deny.
## Two commits to watch
The run remains `running` while it waits. `answered` means the control plane durably accepted one
answer; `delivered` means it handed that answer to the agent service or its durable replay journal.
Only the run's later terminal status tells you how the work ended.
Content-identical waits can share this review unit; the diagram shows the ordinary single-run path.
1. **Person → control plane.** The first valid answer moves the row from `pending` to `answered`
and returns `202 Accepted`.
2. **Control plane → agent.** Tanomis restores a suspended session if needed, delivers the answer
idempotently by `interaction_key`, and moves the row to `delivered` after a successful handoff.
A `202 Accepted` response moves the durable row to `answered`; it does not collapse these two
commits or prove that the agent received the answer. Neither commit approves the final action: a
delivered `approve` lets the agent continue toward a tool call, and the run's terminal status says
whether the work succeeded.
## 1. Discover the pending interaction
Keep the run's event stream open, and independently list durable pending rows. The row is the
recovery source after a disconnect or reload:
```bash
curl --silent --show-error --fail-with-body \
"$TANOMIS_API/v1/sessions/$SESSION_ID/interactions?status=pending" \
--header "Authorization: Bearer $TANOMIS_TOKEN" \
--header "X-Organization-ID: $TANOMIS_ORG" \
--header "X-Workspace-Tenant-ID: $TANOMIS_WORKSPACE"
```
```json title="A permission row"
{
"interaction_key": "ihk_4f72",
"run_id": "run_a12bc3",
"kind": "permission",
"status": "pending",
"payload": {
"tool_name": "Bash",
"tool_input": {
"command": "kubectl rollout restart deployment/api -n production"
}
},
"created_at": "2026-09-04T08:31:12Z",
"expires_at": "2026-09-04T09:31:12Z"
}
```
```json title="A question payload"
{
"question": "Which region should receive the canary?",
"header": "Choose a region",
"options": [
{ "label": "eu", "description": "EU production" },
{ "label": "us", "description": "US production" }
],
"multi_select": false
}
```
`interaction_key` is an opaque, session-scoped review address. Store it with the session and run
IDs exactly as returned and URL-encode it as one path segment; never derive it, use an event's
request ID, or reuse it in another session. The key identifies interaction **content**, so
content-identical asks in one session can share a row and one answer settles every matching ask
parked at that moment. A row therefore proves one **review unit**, not one tool execution. If policy needs a separate
decision for every repeated identical action, give each its own session and run.
## 2. Validate before showing a decision
| Check | Safe behaviour |
|---|---|
| Session and tenant | The session URL and selected tenant are the ones you intended. |
| `run_id` | Matches the run whose context you are showing. |
| `interaction_key` | The exact opaque key from the row. |
| `kind` | `permission` or `question`; reject anything else. |
| `status` | Still `pending`; a terminal row is not actionable. |
| `expires_at` | Still in the future; use the row's value, not an assumed TTL. |
| `payload` | Permission: show the tool and input. Question: show the prompt and options. |
An oversized payload is abridged to `tool_name` plus `"payload_omitted": true`. Do not turn missing
context into a blanket approval; recover context through your application or deny. Escape payload
text when rendering it: it is agent-produced review material, not trusted HTML.
## 3. Submit exactly one kind-specific answer
```text
POST /v1/sessions/{session_id}/interactions/{interaction_key}/answer
```
```json title="Permission"
{
"kind": "permission",
"answer": {
"decision": "approve",
"reason": "Reviewed change request CR-1842."
}
}
```
```json title="Question"
{
"kind": "question",
"answer": "eu"
}
```
To refuse a permission, send `"decision": "deny"` with a useful reason. For a question, `answer` is the string
itself—not an object containing a second `answer` field. A multi-select answer is the chosen labels
as one comma-separated string. Send
`kind` so a mismatch fails before the answer is stored, and keep the encoded answer at or below
65,536 bytes. The API stores `answer` as opaque JSON and validates the row's `kind`, not the inner
shape, so validate that shape in your backend. The first `pending → answered` transition wins;
disable the controls after submitting, but expect another tab or reviewer to win the race.
## 4. Reconcile acceptance and ambiguity
On `202 Accepted`, show **Answer accepted**, not **Action approved**. Then list the session's
interactions and follow the same key until it settles. Use the same read-after-write when the POST
times out; never send a different answer because the client missed the response.
```bash
curl --silent --show-error --fail-with-body \
"$TANOMIS_API/v1/sessions/$SESSION_ID/interactions" \
--header "Authorization: Bearer $TANOMIS_TOKEN" \
--header "X-Organization-ID: $TANOMIS_ORG" \
--header "X-Workspace-Tenant-ID: $TANOMIS_WORKSPACE"
```
| POST or row result | Meaning | Next action |
|---|---|---|
| `202`, row `answered` | One answer is durably accepted; delivery is pending. | Poll with backoff and keep observing the same run. |
| row `delivered` | Tanomis handed the answer to the agent runtime or its replay journal. | Continue to the run's terminal status. |
| `409 interaction_already_answered` | Another caller won the first-answer race. | Read the row and use the recorded answer. |
| `409 interaction_terminal` | The row is `expired` or `cancelled`. | Retire the UI action and read the session and run. |
| `404` | The key or session is absent in the selected tenant; cross-tenant existence is hidden. | Recheck tenant context and stored identifiers. |
| `400` | The filter or top-level kind is invalid, or the answer is absent, malformed, or oversized. | Correct the request. |
The row records the answer and timestamps, but it does not expose an approver identity. If your audit needs the person
and business reason, authenticate them in your application and keep that record beside the
session, run, and interaction keys.
## 5. Let Tanomis attempt to restore the waiting runtime
Do not call `POST /resume` for an interaction wait. If Tanomis suspended the runtime while the
person was thinking, the delivery worker makes a bounded number of restore attempts; after that,
the row stays retryable and delivery waits until another supported request makes the session live.
`answered` can therefore persist; only `delivered` proves the handoff.
Keep the original event stream, reconnecting with its stored `Last-Event-ID` and bounded backoff,
and read the original run until it is `succeeded`, `failed`, or `cancelled`. A pending interaction
adds no run status: the run stays `running`, and a session read may show `awaiting_input: true`.
Do not start a replacement run. If a row stays `answered` beyond your threshold, keep the session,
run, and interaction keys and contact your platform operator.
Your completion record should pair the interaction key, kind, accepted answer, and
`answered_at`/`delivered_at`; the session and run IDs; the run's terminal status; and the terminal
event or application outcome you rely on.
## 6. Close expired and cancelled requests safely
After `expires_at` passes, retire the controls and refresh the row. Expiry and answer race safely:
whichever claims the pending row first stands. An `expired` row is no longer answerable; Tanomis
winds the waiting agent down with a synthetic denial or an empty answer, and the row stays
`expired`. Never present that synthetic value as a human decision.
A row becomes `cancelled` when its run or session ends before it is consumed, including an accepted
row whose session became terminal while delivery was queued: the human answer was stored but the agent did not receive it. For either terminal state, disable the controls, preserve the record, and read
the session and run before offering a new operation. A new operation is a new run; a fresh decision
on content-identical work belongs in a new session.
## Application checklist
- Create an explicit session and run; store both IDs before processing events.
- List `pending` rows independently of the event stream; deduplicate by session plus
`interaction_key`.
- Validate tenant, run, kind, status, expiry, and payload before rendering the decision.
- Enforce your approver roles and escape agent-produced content.
- Send the exact kind-specific answer once.
- Treat `202` as acceptance; reconcile the row through `delivered`, `expired`, or `cancelled`.
- On timeout or `409`, read the winning row; never replace its answer.
- Let Tanomis restore a suspended wait; require `delivered`, then follow the same run to
an authoritative terminal state.
## Where to go next
- [Human interactions in the Platform API](/api/interactions/list-interactions): the exact fields.
- [Answer approvals in the Console](/guides/run/answer-approvals-in-the-console): the same
decision without writing code.
- [Sessions and runs](/concepts/sessions-and-runs#handle-a-run-waiting-for-a-person): the
surrounding lifecycle.
---
Source: /guides/run/attach-files
# Attach files to a session
Give an agent documents to work on: prepare the file, upload its bytes, and reference it when you start a run.
A session can hold files that runs in that session read. Files are first-class resources with their
own ids, integrity checks, and lifecycle, so an upload is never a blind write into an agent's
workspace.
- Check `session_files_supported` on the session first.
- Prepare a file with its name, size, and `sha256`, upload the bytes, then reference its `file_id`
in a run's `attachments`.
- Files belong to the session; list, download, and delete them there.
> **Availability:** Read `file_attachments_enabled` from [Inspect the caller](/api/workspaces/whoami), then `session_files_supported` on the session; the deployed version must also report file support. See [What is available where](/reference/availability).
## 1. Check support on the session
Create the session as usual, then read it. Continue only if `session_files_supported` is `true`.
## 2. Prepare the file
Declare the file before sending bytes, with its size and a SHA-256 digest of the content:
```json title="POST /v1/sessions/{session_id}/files"
{
"filename": "case-summary.pdf",
"media_type": "application/pdf",
"size_bytes": 48213,
"sha256": "83e0ef2f426dfad68f9b420304fc19414f8451a6bf24667f7e61c50e9ca57f14",
"idempotency_key": "case-summary-v1"
}
```
The response carries the file's `id`, its `status` (`prepared`), and a `content_url`.
## 3. Upload the bytes
Send exactly `size_bytes` bytes to the content URL as `application/octet-stream`:
```bash title="Terminal"
curl -X PUT "$TANOMIS_API/v1/sessions/$SESSION_ID/files/$FILE_ID/content" \
-H "Authorization: Bearer $TANOMIS_TOKEN" \
-H "X-Organization-ID: $TANOMIS_ORG" \
-H "X-Workspace-Tenant-ID: $TANOMIS_WORKSPACE" \
-H "Content-Type: application/octet-stream" \
--data-binary @case-summary.pdf
```
The file becomes `ready` when the digest matches what you prepared. A size or digest mismatch is
refused rather than stored.
## 4. Reference it in a run
```json title="POST /v1/sessions/{session_id}/runs"
{
"input": { "prompt": "Summarize the attached case." },
"attachments": [{ "file_id": "file_b172c9" }],
"idempotency_key": "case-summary-run-v1"
}
```
The agent sees the file in its session workspace. Files belong to the session: a run in another
session cannot reference them.
`GET /v1/sessions/{session_id}/files` lists the file with `"status": "ready"` before you start the
run. A `prepared` file that never became `ready` was not uploaded completely.
## Manage files
- **List**: `GET /v1/sessions/{session_id}/files`
- **Download**: `GET /v1/sessions/{session_id}/files/{file_id}/content`
- **Delete**: `DELETE /v1/sessions/{session_id}/files/{file_id}`
## Where to go next
- The endpoints with samples: [Prepare a file](/api/files/prepare-file) and the rest of the
Files group in the [Platform API](/api).
- [Invoke from your application](/guides/run/invoke-from-your-application) for the surrounding
session and run flow.
---
Source: /guides/run/authenticate-your-backend
# Authenticate a backend
Discover OAuth settings and use an organization machine client to authenticate platform API requests.
Create an [automation client](/guides/deploy/automation-credentials) with the `invoke` profile for
an application that runs agents, or `observe` for a read-only integration. Keep its secret on your
backend. Never embed it in browser code or a mobile application.
Your service needs three configuration values: the platform API origin, client ID, and client
secret. For hosted Tanomis, the origin is `https://api.tanomis.com`. If you use `tanomis` in your
service, the [three environment variables](/guides/deploy/automation-credentials#give-tanomis-three-values)
handle the following steps automatically.
- Three values: the platform API origin, the client ID, and the client secret. Never in a
browser or a mobile app.
- Discover the token endpoint from the platform; exchange the credentials with OAuth client
credentials; send the token as a bearer with a tenant selector.
- Effective permissions are the intersection of the token's scopes and the client's current
permissions; a denial is an access decision, not a reason to fetch more tokens.
## Discover authentication settings
Request the public configuration from your trusted platform origin:
```http
GET /v1/cli-config HTTP/1.1
Host: api.tanomis.com
```
The response's `m2m` object supplies `issuer`, `audience`, and `scopes`. If it is missing, machine
authentication is not available on that deployment. Fetch the issuer's OpenID Connect discovery
document at `issuer + /.well-known/openid-configuration` and read `token_endpoint`. For deployments
whose issuer is a base origin, `tanomis` also supports discovery at
`issuer + /oidc/.well-known/openid-configuration` if the first location is unavailable.
Do not substitute the audience for the platform origin. An audience is the identifier of the
resource receiving the token and need not be a reachable address. Discover it rather than
hardcoding a provider's domain or a hosted deployment's identifier.
## Exchange the credentials
Use your backend's HTTP or OAuth library to POST to the discovered token endpoint with HTTP Basic
authentication using the client ID and secret. Send form-encoded fields:
```text
Content-Type: application/x-www-form-urlencoded
Authorization: Basic
grant_type=client_credentials
resource=
scope=
```
The body above lists the fields separately for readability; your library must URL-encode them
as one form. Requesting a scope does not grant it: effective permissions are the intersection of
the token's scopes and the client's current permissions.
Use HTTPS for discovery and token requests, validate certificates, and do not forward credentials
through redirects. Read the returned `access_token`, `token_type`, and expiry in memory. Reuse an
unexpired token and obtain a new one before expiry; this flow does not use a human refresh token.
Keep token responses and authorization headers out of application logs.
## Call the platform
Send the token to the platform API origin and select the target tenant for tenant-scoped requests:
```http
GET /v1/agents HTTP/1.1
Host: api.tanomis.com
Authorization: Bearer
X-Tenant-ID:
```
The tenant must be active and ready in the client's organization. Use the public platform API
for agent, session, and run requests. Do not send organization machine tokens directly to an
independent Registry endpoint.
Disabling or deleting the client, removing a scope, or offboarding the tenant can deny a request
while the token is still unexpired. Treat a permission denial as an access decision, not a reason
to endlessly fetch new tokens. Machines cannot call the human-only credential or tenant lifecycle
APIs to restore their own access.
## Where to go next
- [Invoke from your application](/guides/run/invoke-from-your-application): the six requests
that run an agent.
- [Automation credentials](/guides/deploy/automation-credentials): profiles, rotation, revocation.
- [Platform API](/api): every endpoint.
---
Source: /guides/run/delegate-to-another-agent
# Delegate work to another agent
Let one agent hand a unit of work to another with narrowed authority, wait for the result, and keep every decision in the audit trail.
A supervisor agent can hand a task to a specialist agent: "summarize this thread", "reconcile this
invoice". Tanomis treats the hand-off as a governed call between two versions in the same tenant,
with the callee's authority narrowed to what the caller was allowed to give it.
> **Availability:** Runtime delegation is enabled per platform deployment; where it is off, the delegation tools are absent from the agent's bind. See [What is available where](/reference/availability).
- The **caller** lists the agents it may hand work to under `delegation.calls`.
- The **callee** lists who may call it under `delegation.accepts`, and optionally the `skills`
it offers. Without skills it exposes one default skill that takes a prompt.
- The two manifests are the grant. Nothing is created out of band, and nothing has to be
redone when either side deploys a new version.
- Every attempt, admitted or refused, is a ledger event carrying the full authority chain.
## Declare the two halves
The callee advertises what it accepts:
```yaml title="agent.yaml (callee)"
delegation:
accepts:
- tenant
skills:
- name: summarize
description: Summarize a support thread and return the key facts and the customer's ask.
policy:
scope_ceiling:
- "llm:your-model-family:invoke"
```
The caller names the agents it may call:
```yaml title="agent.yaml (caller)"
delegation:
calls:
- summarizer
policy:
scope_ceiling:
- "llm:your-model-family:invoke"
```
Naming a target puts the delegation tools on the caller's bind and is the caller-side ceiling: an
agent cannot reach a callee it did not name. Whether any particular call is permitted is still
decided per call. Renaming a skill is a breaking change for every caller that lists it.
## How a call is decided
On each delegation the control plane, not the agents, decides:
1. The caller and callee versions are resolved from platform-attested identities, not from
anything the agent claims. The callee is the version currently enabled for the tenant.
2. The caller's manifest must name the callee (and, if narrowed, the skill) under `calls`, and
every enabled callee version must name the caller under `accepts` and offer the skill with
one contract.
3. The callee's effective authority is the intersection of what the caller holds and the callee's
own `scope_ceiling`. A scope the caller does not hold is a hard refusal, never a silent trim. An
agent with no ceiling cannot receive a delegated grant at all.
4. Tenant policy is evaluated with the tenant's posture.
5. The decision is recorded, and only then is the callee's run dispatched.
Authority narrows at every hop and never widens. A callee that delegates further can only pass on
what it was granted.
## What the caller does at run time
The caller sees a small set of tools:
| Tool | Purpose |
|---|---|
| `start_delegation` | Hand a skill invocation to a callee and receive a delegation id |
| `await_delegation` | Wait for the result; the caller can be suspended while it waits, so waiting costs nothing |
| `check_delegation` | Read the state without waiting |
| `list_delegations` | See the delegations this run started |
| `update_delegation`, `cancel_delegation` | Adjust or withdraw work that has not completed |
The callee runs as its own session and run in the same tenant, with the same event stream,
approvals, and evidence as any other run. The caller's run resumes when the result arrives.
## Evidence
Each attempt produces one `agent.delegate` audit event with the caller, the callee, the skill,
the narrowed scopes, the authority chain, and the decision, whether it was admitted or refused. Read
it with `tanomis org audit`; see [Audit and evidence](/guides/audit/audit-and-evidence).
## Where to go next
- [Identity and access](/concepts/identity-and-access): the two identities on every request and
why delegated authority narrows.
- [Agent manifest](/reference/agent-manifest#agent-to-agent-delegation): every field in the
delegation block.
---
Source: /guides/run/durable-work-and-settlement
# Durable work and settlement
Bind a run to a unit of durable work that outlives the session, continue it later, and understand what settlement mode adds.
Most agents answer a request and are done. Some serve a piece of work that has its own identity:
an invoice to reconcile, a case to close. Durable work gives that unit a record the platform
manages, so a later session can continue it and its outcome is settled once.
> **Availability:** Durable work and settlement are enabled per tenant. A `settlement`-mode version fails session activation where the boundary is unavailable rather than running without it. See [What is available where](/reference/availability).
- `settlement_mode` in the manifest is `standard`, `work`, or `settlement`, in that order of
strength. Missing means `standard`.
- In `work` and `settlement` modes, the first run in a new session declares the work it serves:
`work.subject` to open new work, or `work.work_id` to continue existing work.
- The admitted run returns `settlement_work_id`. Store it if another session may continue the
work.
- Work ends in one of two ways decided by the platform, never by a person editing its state.
## Declare the mode
```yaml title="agent.yaml"
settlement_mode: work
```
| Mode | Behaviour |
|---|---|
| `standard` | The existing behaviour: runs are units of execution and nothing more. |
| `work` | The agent produces durable work records. Runs bind to work. |
| `settlement` | As `work`, and the session must establish the managed worker and proxy boundary or fail activation, so every effect the work dispatches is enforced through policy. |
## Open or continue work
```json title="POST /v1/sessions/{session_id}/runs"
{
"input": { "prompt": "Reconcile invoice INV-1842." },
"work": {
"subject": "invoice:INV-1842",
"contract_ref": "billing/reconciliation",
"contract_version": "2026-09"
},
"idempotency_key": "invoice-1842-attempt-1"
}
```
Send either `subject` or `work_id`, never both. When opening work, `contract_ref` and
`contract_version` may name a contract and must be sent together; later requests may omit the
pair or restate it exactly, but cannot add or change it. The response's `settlement_work_id` is
the handle another session uses with `work.work_id`.
A work item has at most one live run at a time; a second run against the same work while one is
live returns `409` with `work_has_live_run`.
## What settlement adds
In `settlement` mode the effects a run dispatches to other systems, not the messages it exchanges,
are the things being settled. Each is authorized through the policy facade before it happens and
recorded as it happens, and the work's outcome is decided from those records. Work ends two ways:
it settles, or it is abandoned by the platform's rules. A person does not end it by hand, which is
what makes the record trustworthy.
## Where to go next
- [Sessions and runs](/concepts/sessions-and-runs): where work sits beside sessions and runs.
- [Start a run](/api/runs/start-run): the request fields.
- [Agent manifest](/reference/agent-manifest#state-warm-starts-and-sessions): the mode field.
---
Source: /guides/run/event-triggers
# Event triggers
Start a run when something happens elsewhere: a webhook, a queue message, or a platform event, with the payload delivered to the agent.
Event triggers start a run in response to an external or platform event instead of a direct call. The event's payload reaches the agent as the run's input, and the trigger is governed and audited like any other way of starting a run.
This page is planned and not yet written, because the capability it describes is not yet
generally available. The structure is here so you can see where it fits; the notice comes off
when it ships.
## What this page will cover
- Trigger sources: webhooks, queues, and platform events such as a run completing.
- Verifying and shaping the incoming payload before it reaches the agent.
- Mapping an event to a session: new session per event or continue an existing one.
- Retries, deduplication, and how triggered runs appear in evidence.
## Meanwhile
- [Invoke from your application](/guides/run/invoke-from-your-application) shows how to start a run from your own webhook receiver today.
- [Stream and replay events](/guides/run/stream-and-replay-events) covers the events a run emits, which a trigger could react to.
- [Delegate work to another agent](/guides/run/delegate-to-another-agent) is the governed way one agent starts work in another today.
---
Source: /guides/run/fork-a-session
# Fork a session
Start a new session from a point in an existing session's history, with its own lineage and evidence.
The Console can fork a session from a point in its timeline. The API surface for forks is not yet part of the public Platform API.
This page is planned and not yet written, because the capability it describes is not yet
generally available. The structure is here so you can see where it fits; the notice comes off
when it ships.
## What this page will cover
- Fork from the Console and from the API.
- What the fork inherits: workspace files, history, and identity.
- How lineage appears in evidence.
## Meanwhile
- [Sessions and runs](/concepts/sessions-and-runs) explains why a later run is not a fork.
- [Tour the Console](/getting-started/console-tour) shows where forking lives.
---
Source: /guides/run/invoke-from-your-application
# Invoke from your application
Call a deployed agent from your own backend: discover the workspace, create a session, start a run, and follow it to a result.
Once a version is enabled for a tenant, your product calls it over HTTP. The sequence is the same
for every agent: discover your workspace, create a session, start a run, follow its events, read
the result.
You need an access token for the Tanomis API audience and an agent enabled for the tenant you
will select. A backend gets its token from an automation client with the `invoke` profile; see
[Authenticate a backend](/guides/run/authenticate-your-backend). The CLI's human login is not an
application credential.
## 1. Discover the workspace once
Ask which organizations and tenants the token may use, and keep the pair you choose:
```bash title="Terminal"
curl "https://api.tanomis.com/v1/me/workspaces" \
-H "Authorization: Bearer $TANOMIS_TOKEN"
```
Copy `organization_id` and the selected tenant's `tenant_id` into the two workspace headers on
every later request:
```text
X-Organization-ID: org_abc123
X-Workspace-Tenant-ID: customer-production
```
The headers select context. They cannot widen what the token grants.
## 2. Create a session
A session is the continuity boundary for one enabled agent: it keeps the agent's workspace, serializes
runs, and is what you retry against.
```bash title="Terminal"
curl -X POST "https://api.tanomis.com/v1/sessions" \
-H "Authorization: Bearer $TANOMIS_TOKEN" \
-H "X-Organization-ID: org_abc123" \
-H "X-Workspace-Tenant-ID: customer-production" \
-H "Content-Type: application/json" \
-d '{"agent_name":"support-copilot","idempotency_key":"support-case-1842"}'
```
Send an `idempotency_key` tied to your own unit of work. A retry with the same key and body
returns the same session instead of creating another.
## 3. Start a run
```bash title="Terminal"
curl -X POST "https://api.tanomis.com/v1/sessions/$SESSION_ID/runs" \
-H "Authorization: Bearer $TANOMIS_TOKEN" \
-H "X-Organization-ID: org_abc123" \
-H "X-Workspace-Tenant-ID: customer-production" \
-H "Content-Type: application/json" \
-d '{"input":{"prompt":"Draft a reply to this customer."},"idempotency_key":"support-case-1842-reply-1"}'
```
The response is the admitted run. Its `id` and `status` are the authoritative state from here on.
## 4. Follow the run
Stream the run's events as server-sent events and store the last event id you processed:
```bash title="Terminal"
curl --no-buffer "https://api.tanomis.com/v1/sessions/$SESSION_ID/runs/$RUN_ID/events" \
-H "Authorization: Bearer $TANOMIS_TOKEN" \
-H "X-Organization-ID: org_abc123" \
-H "X-Workspace-Tenant-ID: customer-production" \
-H "Accept: text/event-stream"
```
If the connection drops, reconnect with `Last-Event-ID` and the stream resumes after it. The
terminal events are `run_completed`, `run_cancelled`, and `error`. When a stream ends without one,
read the run and reconnect only while it is still non-terminal. The complete consumer loop,
including deduplication, is in [Stream and replay events](/guides/run/stream-and-replay-events).
## 5. Handle a pause for a person
If the agent reaches an action that needs approval, or asks a question, the run pauses and the
session reports `awaiting_input: true`. List the session's pending interactions, show the decision
to a person, and answer by the interaction's key. The run continues from where it paused. See
[Approval-required actions](/guides/run/approval-required-actions).
## 6. Finish
When no more work will use the session, stop it to release its runtime:
```bash title="Terminal"
curl -X POST "https://api.tanomis.com/v1/sessions/$SESSION_ID/stop" \
-H "Authorization: Bearer $TANOMIS_TOKEN" \
-H "X-Organization-ID: org_abc123" \
-H "X-Workspace-Tenant-ID: customer-production"
```
A stopped session can be resumed later. Idle sessions are suspended automatically and restored
when work arrives, so you do not have to stop a session between turns of the same conversation.
## Choosing between one-shot and sessions
| You want | Use |
|---|---|
| One independent request, streamed, no retries to reason about | [Invoke once and stream](/api/agents/invoke-agent) |
| Retries, several runs over the same context, file attachments, approvals, reconnection | An explicit session, as above |
## Where to go next
- [Platform API](/api): every endpoint with request and response samples in cURL, Python, and
TypeScript.
- [Sessions and runs](/concepts/sessions-and-runs): state, retries, concurrency, recovery.
- [Attach files to a session](/guides/run/attach-files): give the agent documents to work on.
---
Source: /guides/run/scheduled-execution
# Scheduled execution
Run an agent on a schedule without a caller: define a cadence, choose what each run starts with, and follow the runs it produces.
Today every run starts because a caller asks for it: your backend, the Console, or the CLI. Scheduled execution lets the platform start runs on a cadence you declare, with the same session, policy, and evidence rules as a run you start by hand.
This page is planned and not yet written, because the capability it describes is not yet
generally available. The structure is here so you can see where it fits; the notice comes off
when it ships.
## What this page will cover
- Declare a schedule for a deployed version and the tenants it runs for.
- What each scheduled run starts with: prompt, workspace, and durable work.
- Overlap, missed windows, and pausing a schedule.
- How scheduled runs appear in events and evidence.
## Meanwhile
- [Invoke from your application](/guides/run/invoke-from-your-application) shows how to start runs from your own scheduler today.
- [Automation credentials](/guides/deploy/automation-credentials) covers the machine identity a scheduler would use.
- [Durable work and settlement](/guides/run/durable-work-and-settlement) explains how a later run continues the same unit of work.
---
Source: /guides/run/stream-and-replay-events
# Stream and replay events
Consume Tanomis run events, persist a cursor, reconnect safely, and confirm durable run status.
Run events let your application render progress while an agent works. Treat the stream as a live
delivery channel—not as the authoritative run record or a permanent audit log. A reliable consumer
processes each event idempotently, stores a run-scoped cursor, and reads the run after every terminal
signal or end-of-stream.
This guide assumes you already have a session ID, run ID, and a supported access-token path. See
[Sessions and runs](/concepts/sessions-and-runs) first if those resource boundaries are unfamiliar.
- Open the stream with `Accept: text/event-stream`; every frame has an id, an event name, and the full event as JSON.
- Store the last id you processed **after** processing it, and deduplicate by run id plus event id.
- On disconnect, reconnect with `Last-Event-ID`; replay starts strictly after it.
- Terminal events are `run_completed`, `run_cancelled`, and `error`. A stream can end without one, so the run resource decides.
Event `41` is processed and stored → event `42` is processed → the connection breaks before cursor
`42` is stored → reconnect sends `Last-Event-ID: 41` → event `42` arrives again and is ignored by
deduplication → event `43` signals `run_completed` → `GET` run confirms authoritative status
`succeeded`.
If cursor `42` had been stored successfully, reconnecting with `Last-Event-ID: 42` would begin
strictly after it. The duplicate above exists because application work completed before its cursor
checkpoint did—not because the server replays the requested cursor.
## Open one run stream
Read events for one session and run:
`GET /v1/sessions/{session_id}/runs/{run_id}/events`
```bash
curl --no-buffer --fail-with-body \
"$TANOMIS_API/v1/sessions/$SESSION_ID/runs/$RUN_ID/events" \
--header "Authorization: Bearer $TANOMIS_TOKEN" \
--header "X-Organization-ID: $TANOMIS_ORG" \
--header "X-Workspace-Tenant-ID: $TANOMIS_WORKSPACE" \
--header "Accept: text/event-stream"
```
The stream is scoped to this session and run. It does not provide ordering across runs or combine
events from other sessions.
## Read complete SSE frames
Wait for the blank line that ends a frame before processing it. Every public event frame contains:
| SSE field | Contract |
|---|---|
| `id` | An opaque event ID. It orders events only inside this run. |
| `event` | The event name. Only the three terminal names below have stable terminal meaning. |
| `data` | One complete JSON event object, including its own `id`, `type`, `session_id`, `run_id`, and harness-specific `data`. |
A synthetic stream can look like this:
```text
id: 41
event: message_delta
data: {"id":"41","type":"message_delta","session_id":"ses_example","run_id":"run_example","created_at":"2026-09-03T09:00:00.041Z","data":{"text":"Draft"}}
id: 42
event: message_delta
data: {"id":"42","type":"message_delta","session_id":"ses_example","run_id":"run_example","created_at":"2026-09-03T09:00:00.042Z","data":{"text":" ready"}}
id: 43
event: run_completed
data: {"id":"43","type":"run_completed","session_id":"ses_example","run_id":"run_example","created_at":"2026-09-03T09:00:00.043Z","data":{}}
```
`message_delta` is illustrative, not a universal schema promise. Event names and fields inside the
harness-specific `data` object can evolve.
## Reconnect without double-applying
Use this sequence for every connection:
1. Open the events endpoint with `Accept: text/event-stream`. If a cursor exists for this run, send
it in `Last-Event-ID`.
2. Parse one complete frame. Verify its session and run IDs match the resource you requested.
3. Build a deduplication key from the run ID and event ID. If that key is already processed, skip
the side effect and advance the stored cursor if needed.
4. Apply the event idempotently. Only after processing succeeds, record the deduplication key and
persist the event ID as this run's cursor.
5. On `run_completed`, `run_cancelled`, or `error`, stop interpreting the stream and read the run
resource for its authoritative lifecycle status and error details.
6. On a clean EOF without a terminal event, read the run. Reconnect with bounded backoff only while
the run remains `queued` or `running`.
Store the cursor with both the session ID and run ID. Treat the event ID as an opaque string; never
compare IDs from separate runs. Where an event changes durable application state, make the state
change and cursor checkpoint atomic when possible. Otherwise, make the handler idempotent so a
replayed frame is safe.
Reconnect by sending the last **successfully processed** event ID:
```http
Accept: text/event-stream
Last-Event-ID: 41
```
When live or persisted history is available, replay starts strictly after that ID.
## Backend consumer pseudocode
Keep bearer credentials and cursor storage in your trusted backend. This pseudocode names the
required behavior without prescribing an SDK or storage engine:
```text
cursor = load_cursor(session_id, run_id)
loop:
stream = open_events(session_id, run_id, last_event_id=cursor)
for frame in stream:
event = parse_complete_json_frame(frame)
assert event.session_id == session_id and event.run_id == run_id
key = (run_id, event.id)
if already_processed(key):
cursor = checkpoint(event.id)
continue
apply_idempotently(event)
record_processed_and_checkpoint(key, event.id)
cursor = event.id
if event.type in {run_completed, run_cancelled, error}:
break
run = get_run(session_id, run_id)
if run.status in {succeeded, failed, cancelled}:
return run
wait_with_bounded_backoff()
```
Do not start a new run when this stream disconnects. Reconnect to the same run with its cursor.
## Connect from a browser deliberately
The browser's native `EventSource` constructor cannot attach an arbitrary `Authorization` header.
Do not put bearer credentials in a query string to work around that boundary.
Choose an architecture that matches your application's security model:
- Stream through your authenticated same-origin backend, which keeps the Tanomis bearer token
server-side and forwards only the events the signed-in user may see.
- Use a fetch-based SSE parser only when the API is same-origin or your environment administrator
explicitly configures CORS for your frontend origin and the required request headers. Your
application must also be designed to hold and send the token in that browser context.
Do not assume the hosted API accepts cross-origin browser requests. The same frame, cursor,
deduplication, and final-read rules still apply.
Browser tab closure, page navigation, and sleep are ordinary disconnects. Persist the run-scoped
cursor somewhere that survives the recovery behavior your product promises.
## Handle closure, replay, and change
| What you observe | What it means | What to do |
|---|---|---|
| A previously processed event arrives again | The stored cursor lagged successful processing, or the consumer is recovering around a failure boundary. | Deduplicate by run ID plus event ID; do not repeat the side effect. |
| `200 OK` closes with zero frames | History may be absent, exhausted, or temporarily unreadable. This is a normal, fail-soft EOF—not proof of success. | Read the run resource. Reconnect boundedly only if its status is non-terminal. |
| The stream closes after non-terminal events | Delivery ended before a terminal signal reached this connection. | Preserve the last processed cursor, read the run, and reconnect to the same run if needed. |
| `run_completed`, `run_cancelled`, or `error` arrives | The stream emitted a terminal signal. | Stop consuming and read the run for authoritative lifecycle status and error details. |
| An unknown event name or JSON field appears | A compatible harness or platform addition is present. Unknown events are not terminal. | Ignore it or render it generically; preserve fields you proxy and keep consuming. |
| A frame cannot be parsed or fails scope checks | The frame was not processed successfully. | Do not advance the cursor; close the connection, inspect the run, and reconnect boundedly. |
Persisted replay is best-effort and fail-soft. It does not guarantee that every prior event remains
available, so do not use the event stream as your audit record. The final run resource tells you the
durable lifecycle status even when the detailed transcript is unavailable.
It does not reconstruct agent output or event payloads that your consumer missed.
## Protect event content
Treat `data` as potentially sensitive customer and agent content. Depending on the harness and run,
it can carry user input, model output, or tool-related details. Apply your product's authorization,
redaction, retention, and display rules before storing or logging it. Avoid raw-frame logging by
default; identifiers and event names are usually enough for transport diagnostics.
## Where to go next
- [Stream run events](/api/runs/stream-run-events): exact request and response shapes.
- [Sessions and runs](/concepts/sessions-and-runs): lifecycle, retries, cancellation.
- [Approval-required actions](/guides/run/approval-required-actions): when the stream pauses
for a person.
---
Source: /guides/run/webhooks
# Webhooks
Receive an HTTP request from Tanomis when a lifecycle event happens, such as a run finishing or an approval being requested, instead of polling.
Today your application learns that a run finished by following its event stream or polling its status. Webhooks let Tanomis call an endpoint you register when a lifecycle event happens, such as a run completing, failing, or pausing for an approval, so your backend can react without holding a connection open.
This page is planned and not yet written, because the capability it describes is not yet
generally available. The structure is here so you can see where it fits; the notice comes off
when it ships.
## What this page will cover
- Register an endpoint for a tenant and choose which lifecycle events it receives.
- The delivery payload and how to verify its signature.
- Retries, ordering, and deduplicating a delivery you have already handled.
- How deliveries appear in evidence.
## Meanwhile
- [Stream and replay events](/guides/run/stream-and-replay-events) is how your application follows a run to completion today.
- [Invoke from your application](/guides/run/invoke-from-your-application) covers the run lifecycle a webhook would report on.
- [Event triggers](/guides/run/event-triggers) is the inverse: an event elsewhere starting a run.
---
Source: /reference/agent-manifest
# Agent manifest
Current agent.yaml settings, with types, defaults, validation rules, and effects.
`agent.yaml` is the deployable contract for one agent version. This page describes the current authoring fields: what each setting accepts, what it defaults to, and what rejects a manifest at registration.
For a guided introduction to the fields you touch first, read
[Manifest essentials](/guides/build/manifest-essentials).
Two rules apply everywhere:
- **Unknown top-level keys are rejected.** A typo fails the manifest rather than being ignored. The
one exception is a key prefixed `x-`, which is accepted and carried through untouched.
- **A manifest is validated against a version, not an agent.** Settings are captured at
registration and are immutable for that version; you change them by publishing a new version.
## A complete manifest
Most agents need about this much. Every field links to the section that defines it.
```yaml title="agent.yaml"
name: support-copilot # stable agent identity
version: 0.2.0 # labels one immutable build
framework: claude-agents-sdk # harness adapter
entrypoint: agent:create_agent # python_module:function
description: Triage and draft responses to support requests
kind: interactive # interactive | background
identity_mode: service # pinned by the first version
model: claude-sonnet
permitted_models: [claude-sonnet, claude-haiku]
persistent: true # required for hosted versions
session:
idle_timeout: 30m
max_lifetime: 24h
resources:
cpu: 100m
memory: 512Mi
network:
egress:
- api.zendesk.com # bare hostname, within the tenant ceiling
policy:
approval_required_tools: [Bash] # a person decides before these run
enable_ask_user: true
scope_ceiling: [llm:claude-sonnet:invoke]
allowed_audiences: [https://api.tanomis.com]
integrations:
support:
connector: global/support-desk # use an existing configured connector
required: true
tools:
allow: [search_tickets, draft_reply]
approval_required: [draft_reply]
```
## Field index
Grouped by the section that defines each field. "Hosted: yes" means the hosted control plane
requires it; lenient non-hosted profiles may default it.
### Identity and packaging
| Field | Type | Required | Default |
|---|---|---|---|
| [`name`](#identity-and-packaging) | string | yes | — |
| [`version`](#identity-and-packaging) | string | in practice | — |
| [`framework`](#identity-and-packaging) | enum | yes | — |
| [`entrypoint`](#identity-and-packaging) | string | yes | — |
| [`description`](#identity-and-packaging) | string | no | empty |
| [`kind`](#behavioral-classification) | enum | yes | — |
### Models and tools
| Field | Type | Required | Default |
|---|---|---|---|
| [`model`](#models) | string | no | resolved by the platform |
| [`permitted_models`](#models) | list of string | no | empty |
| [`permitted_tools`](#models) | list of string | no | empty |
### Attribution and identity
| Field | Type | Required | Default |
|---|---|---|---|
| [`identity_mode`](#attribution-and-identity) | enum | hosted: yes | `service` only in lenient non-hosted profiles |
| [`inherits_user_permissions`](#attribution-and-identity) | boolean | no | `false` |
| [`identity.client_id`](#attribution-and-identity) | string | no | assigned at registration |
### State, sessions, and resources
| Field | Type | Required | Default |
|---|---|---|---|
| [`persistent`](#state-warm-starts-and-sessions) | boolean | hosted: yes | `false` only in lenient non-hosted profiles |
| [`cr_enabled`](#state-warm-starts-and-sessions) | boolean | deprecated compatibility field | ignored for runtime selection |
| [`settlement_mode`](#state-warm-starts-and-sessions) | enum | no | `standard` |
| [`session.idle_timeout`](#state-warm-starts-and-sessions) | duration | yes | — |
| [`session.max_lifetime`](#state-warm-starts-and-sessions) | duration | yes | — |
| [`resources.cpu`](#resources) | quantity | yes | — |
| [`resources.memory`](#resources) | quantity | yes | — |
| [`resources.cpu_limit`](#resources) | quantity | no | platform burst default |
| [`resources.memory_limit`](#resources) | quantity | no | platform burst default |
### Network, policy, tools, and delegation
| Field | Type | Required | Default |
|---|---|---|---|
| [`network.egress`](#network) | list of string | no | empty |
| [`network.egress_mode`](#network) | `allowlist` or `public` | no | `allowlist` |
| [`policy.*`](#policy) | block | no | see the section |
| [`integrations`](#governed-mcp-connectors) | map of requirement | no | empty |
| [`access.github`](#github-repositories) | block | no | none |
| [`delegation.*`](#agent-to-agent-delegation) | block | no | empty |
### Metadata, caps, and harness
| Field | Type | Required | Default |
|---|---|---|---|
| [`data_classification`](#declared-capability-metadata) | list of string | no | empty |
| [`cloud_access`](#declared-capability-metadata) | list of string | no | empty |
| [`cost_cap_usd`](#declared-capability-metadata) | number | no | `0` |
| [`token_cap`](#declared-capability-metadata) | integer | no | `0` |
| [`lifecycle_policy`](#declared-capability-metadata) | block | no | empty |
| [`harness.`](#harness-settings) | block | no | harness defaults |
| [`autonomy`](#declared-capability-metadata) | any | no | accepted, not yet applied |
## Identity and packaging
```yaml
name: support-copilot
version: 0.1.0
framework: claude-agents-sdk
entrypoint: agent:create_agent
description: Triage and draft responses to support requests.
```
- **`name`** — the stable agent identity, and the name every other command refers to. Must start
with a letter or digit and contain only letters, digits, underscore, dot, or dash, up to 128
characters. A deploy that names a different agent than the manifest does is rejected.
- **`version`** — the label for one immutable build. Validation does not require it, but deploy
does: supply it here or pass `--version` on the command line. A published version is immutable,
so publish a new label rather than rebuilding an existing one.
- **`framework`** — the harness adapter. One of `claude-agents-sdk` or `deepagents`; confirm the
set your control plane offers with `tanomis agent harnesses`.
- **`entrypoint`** — `module:function`, resolved inside your packaged project. Exactly one colon,
with both halves non-empty.
- **`description`** — free text shown in listings. Optional, and worth writing: it is what a person
scanning the agent catalog reads.
## Behavioral classification
```yaml
kind: interactive
```
**`kind`** is required and is one of:
| Value | Meaning |
|---|---|
| `interactive` | Human in the loop, turn by turn. Latency-sensitive resource profile. |
| `background` | Runs without a person waiting on each turn. |
**`autonomy`** is accepted and stored verbatim but is not yet applied to behavior. Set it only if
you want the declaration on record.
## Models
```yaml
model: "your-workspace-model-id"
permitted_models:
- "your-workspace-model-id"
```
- **`model`** — the default model resolved for a run. `default` follows the organization's
default model, resolved at each session start; any other value pins that model.
- **`permitted_models`** — the runtime allowlist. A run that requests a model outside this list is
refused. Leaving it empty does not widen access; it means the manifest declares no allowlist and
the model decision falls to platform and tenant policy.
Use model IDs available to the selected tenant. Whenever you change `model`, update the matching
`llm::invoke` entry in `policy.scope_ceiling`, or the declared ceiling stops
describing the model the agent actually calls.
## Attribution and identity
```yaml
identity_mode: service
inherits_user_permissions: false
```
- **`identity_mode`** — the agent's intended authority posture. One of `service` (the agent's own
machine identity), `delegated` (intended user subject plus agent actor), or `hybrid`. Hosted
deployments require an explicit value and reject omission; a lenient
non-hosted profile may resolve a missing value to `service`. **The first non-rejected version
pins it** across every later version of the agent; a rejected initial attempt does not establish
the pin, so agree the mode with your organization administrator before the first deploy.
- **`inherits_user_permissions`** — the gated impersonation opt-in. Illegal with
`identity_mode: service`, because there is no user to inherit from; use `delegated` or `hybrid`.
Turning it on seeds an impersonation-approval gate rather than granting anything on its own.
- **`identity.client_id`** — the agent's application identity. On a hosted control plane this is
assigned at registration and an author-supplied value is **rejected**, so omit the `identity`
block entirely.
> **Availability:** Service-mode bind-time identity is enabled per tenant; delegated and scoped authority are held contracts. See [What is available where](/reference/availability).
For the actor model, hybrid-path boundary, denial sequence, and safe attribution guidance, read
[Agent identity and access](/concepts/identity-and-access).
## State, warm starts, and sessions
```yaml
persistent: true
session:
idle_timeout: 30m
max_lifetime: 24h
```
- **`persistent`** — provisions durable tenant-scoped storage and gives each session its own
directory. It is required for new hosted versions.
- **`cr_enabled`** — deprecated. Older manifests may retain it during the internal rolling
deployment, but true, false, and omission do not select a runtime in current runtime-api. The
deploy pipeline and CLI still use `true` temporarily to request a golden build; that legacy
consumer is removed by the golden-policy follow-up. RAM snapshot policy is platform-owned. Until
universal-worker and golden-policy cutover, remote admission requires `true` for settlement or
author-declared egress; that is a rollout barrier, not a tenant-facing compatibility period.
- **`settlement_mode`** — one of `standard`, `work`, or `settlement`, in that order of strength; a
missing value resolves to `standard`. `standard` is the existing behavior. `work` means the agent
produces durable work records. `settlement` requires the session to establish the managed
worker/proxy boundary or fail activation.
- **`session.idle_timeout`** / **`session.max_lifetime`** — both required. Compact Go-style
durations (`45s`, `30m`, `24h`), positive, and resolving to whole seconds. `1500ms` is rejected.
> **Availability:** A snapshot is an optimization, not the execution-boundary selector. Durable work and settlement are enabled per tenant; a settlement-mode session fails activation where the boundary is unavailable. See [What is available where](/reference/availability).
## Resources
```yaml
resources:
cpu: 100m
memory: 512Mi
cpu_limit: 2
memory_limit: 2Gi
```
`cpu` and `memory` are the **requests** — the reserved baseline that drives scheduling and density.
They are required. `cpu` accepts a Kubernetes CPU quantity (`500m`, `2`); `memory` accepts a binary
quantity (`512Mi`, `2Gi`) — decimal suffixes like `500M` are rejected.
`cpu_limit` and `memory_limit` are the optional **burst ceiling**. When unset the platform applies a
generous default so a run can spike above its request. Set them to raise or pin the ceiling for one
agent, subject to tenant policy.
Leave the requests at their scaffolded values until a measurement gives you a reason to change them.
## Network
```yaml
network:
egress:
- github.com
- api.example.com
```
**`network.egress_mode`** defaults to `allowlist`. Set it to `public` to permit arbitrary public
hostnames through the managed proxy. In public mode, `egress` must be absent or empty. An unset
tenant ceiling permits this mode; any configured hostname ceiling rejects it. Private and reserved
addresses remain blocked. Public mode requires capable node/helper versions and the external route;
see [Restrict network egress](/guides/govern/restrict-network-egress#allow-arbitrary-public-websites).
**`network.egress`** is the list of external hostnames this agent may reach. It is part of the
version's contract: captured at registration, immutable for that version, and enforced at run time.
Availability is per tenant. Ask your administrator whether author-declared egress is enabled for
the selected tenant before relying on it; where it is not, the list is still validated and recorded
but no external route exists.
### What an entry may be
Each entry is a bare hostname, written in canonical form — lowercase, no surrounding whitespace,
no trailing dot. Anything else is refused at deploy rather than quietly rewritten, so the value you
wrote is the value that is enforced.
Refused, each with the reason named:
| Not allowed | Why |
|---|---|
| `*.github.com` | Wildcards are not permitted in a declared ceiling. An entry already covers its subdomains. |
| `https://github.com`, `github.com:443`, `github.com/path` | The match is on host alone; a scheme, port, or path would be silently ignored. |
| `140.82.121.4` | An address carries no DNS identity, and the allowlist matches the host an agent asks for. |
| the loopback name, `svc.cluster.local`, `printer.local`, `vault.internal` | Names for the sandbox itself or the in-cluster world. Platform destinations are added for you and are not declarable. |
| `GitHub.com`, `github.com.` | Not canonical. Write `github.com`. |
| `github` | A hostname needs at least two labels. |
**An entry covers its subdomains.** `github.com` admits `api.github.com` and `a.b.github.com`. It
does not admit `notgithub.com` or `github.com.example`. Declare the narrowest name that covers what
you need.
### The tenant ceiling
Your administrator can set a per-tenant ceiling that your declaration is intersected with. It
only ever narrows: it cannot add a destination you did not declare, and it never affects the
platform's own routes.
An unconfigured tenant admits any public hostname, so an unset ceiling behaves as though only
your manifest applied. Where a ceiling excludes an entry, the deploy fails naming both the entry and
the ceiling that excluded it, before any image is built — so you find out in seconds rather than
after a build.
A ceiling of `github.com` admits a declaration of `api.github.com`, because that is narrower than
what the administrator permitted.
### What is enforced, and where
- **At deploy.** The grammar and the tenant ceiling, before a build is created.
- **At rollout.** The ceiling again, when the version is enabled for a tenant — the point at which a
globally published agent first has a tenant to be measured against.
- **At run time.** Every connection is mediated. A destination outside the list is refused. A
declared hostname that resolves to a private, loopback, link-local, or in-cluster address is
refused too, and the connection is made to the address that was checked, so a name whose record
changes cannot redirect it.
The platform's own destinations — the managed model gateway and the governed connector routes — are
always available and are never declared here. Your list can only add to them.
### Requirements
Egress enforcement runs in the managed worker's session-scoped proxy for both snapshot restore and
clean bootstrap. If that boundary cannot be established, activation is refused instead of running
unproxied. During the internal mixed-runtime rollout only, remote admission may require the legacy
`cr_enabled: true` spelling so an older runtime replica cannot accept a contract it cannot enforce.
## Policy
```yaml
policy:
approval_required_tools:
- Bash
enable_ask_user: false
enable_claude_md: false
enable_context_files: false
scope_ceiling:
- "llm:your-model-family:invoke"
allowed_audiences:
- "https://your-workspace-api.example"
approval_thresholds:
- condition: "scope:payments.*"
approver: workspace-admin
```
### Human-in-the-loop
- **`approval_required_tools`** — every listed tool pauses the run and emits a permission request
that the console renders as an allow/deny card. Names are the harness's own tool names.
- **`enable_ask_user`** — exposes the harness question flow, letting the agent ask a person a
question mid-run and wait for the answer.
- **`enable_claude_md`** — `claude-agents-sdk` only. Lets the agent read a project `CLAUDE.md` from
its workspace as a context file. Ignored on other harnesses.
- **`enable_context_files`** — accepted for forward compatibility with a broader context-file
surface. Today the only such surface is gated by `enable_claude_md`.
`approval_required_tools` and `enable_ask_user` produce the durable runtime interactions described
in [Handle approval-required actions](/guides/run/approval-required-actions). That application flow is
separate from `approval_thresholds` below, whose scoped-token completion path remains held.
### Authorization contract
- **`scope_ceiling`** — the maximum set of concrete capabilities this agent's tokens may ever
carry. The broker intersects requested scopes with it; it never widens them. Each entry must
match exactly one of three grammars, and **wildcards are rejected**:
| Form | Example |
|---|---|
| `.` | `calendar.read` |
| `mcp:::` | `mcp:github:create_issue:write` |
| `llm::` | `llm:your-model-family:invoke` |
On a hosted control plane the ceiling is required; where it is optional, an absent ceiling means
allow-all with a warning.
**What the ceiling governs.** It bounds what this agent may do *acting as itself*, in two places:
- **Agent-to-agent delegation.** On a delegated call the caller's ceiling is intersected with the
callee's, and only scopes inside both are granted. An agent with no ceiling cannot receive a
delegated grant at all.
- **Token exchange (held contract).** If a future supported flow exchanges a token against one
of the agent's `allowed_audiences`, a requested scope outside the ceiling is refused. No
supported public runtime workflow currently exposes this scoped exchange.
**What it does not govern.** Three settings look like "what may this agent do", and they are
separate mechanisms — sizing the ceiling for these will not work:
| Concern | Where it is actually enforced |
|---|---|
| Which model a run may use | `permitted_models` |
| Which MCP tools an agent may call | `integrations..tools.allow` intersected with the current workspace grant |
| What the agent may do as itself | **`scope_ceiling`** |
In particular, an `mcp:::` entry is a well-formed ceiling scope, but adding
one does **not** grant an MCP tool call and omitting one does not block it — see
[Governed MCP connectors](#governed-mcp-connectors). Entries are captured at registration and are
immutable for that version, so declare the ceiling you need and widen it in a later version
rather than padding it defensively.
- **`allowed_audiences`** — the `https://` resource indicators the agent's minted tokens may
target. **Required once `scope_ceiling` is non-empty**, and each entry must be an `https://` URI.
Use the audience issued for your tenant/environment; never copy one from another environment.
- **`approval_thresholds`** — maps a sensitivity condition to an approver. In the held delegated
exchange contract, matching a sensitive scope classifies the request as approval-required; no
supported durable approval ID, decision endpoint, or resume path currently completes that
exchange. `condition` is required and is a sensitivity *pattern*, so unlike a grant it may use
wildcards. `approver` is optional and defaults to the invoking user. See [Agent identity and
access](/concepts/identity-and-access#what-the-service-assertion-is-and-is-not)
before designing around these fields.
Both `scope_ceiling` and `allowed_audiences` are re-checked for presence on each new version, so a
new version can widen them — which is exactly the decision an agent's author should be making
deliberately.
## Declared capability metadata
```yaml
permitted_tools: []
data_classification:
- internal
cloud_access:
- object-storage
cost_cap_usd: 25
token_cap: 2000000
lifecycle_policy:
required_gates:
- regression-suite
- prompt-injection-suite
gate_categories:
prompt-injection-suite: adversarial
rollback: manual
```
- **`permitted_tools`** — the tool allowlist carried into the policy decision point. An empty list
declares no manifest-level restriction; enumerate tools to narrow what the agent may run.
- **`data_classification`** and **`cloud_access`** — the data sensitivity and cloud-resource classes
the agent declares. Recorded on the version and surfaced through the catalog for review.
- **`cost_cap_usd`** and **`token_cap`** — declared budgets for the version. Must not be negative.
- **`lifecycle_policy.required_gates`** — the evaluation gates that must pass before the
gate-guarded promotion. **`lifecycle_policy.rollback`** is `manual` or `auto`; empty resolves to
`manual`.
- **`lifecycle_policy.gate_categories`** maps a required gate to the kind of eval suite behind it:
`quality`, `robustness`, or `adversarial`. A gate left out is `quality`. Each key must name a
gate in `required_gates`, and any other value is refused at registration. The version's gate
listing (`GET /v1/agents/{name}/versions/{version}/gates`) returns the category on every gate so
robustness and adversarial suites can be shown apart from quality evals.
`data_classification`, `cloud_access`, `cost_cap_usd`, and `token_cap` are recorded as version
metadata and exposed for governance review. Treat them as the agent's declaration of intent, and
keep the enforcement in `permitted_models`, `permitted_tools`, and `policy.scope_ceiling`.
## Governed MCP connectors
An agent references a configured connector directly. No catalog association, contract publication,
or connection-selection step is needed.
```yaml title="agent.yaml"
integrations:
finance:
connector: global/alphavantage-workspace
required: true
tools:
allow: [SYMBOL_SEARCH, COMPANY_OVERVIEW, EARNINGS]
```
Find the connector and its approved tools:
```bash
tanomis mcp connector list
tanomis mcp connector tools CONNECTOR_ID
```
Use `global/NAME` for a platform connector, `tenant/NAME` for a connector owned by the
agent's workspace, or an exact connector ID. An unqualified name works when unambiguous.
A global agent can reference global connectors; publish a tenant-scoped agent when it needs a
tenant-owned connector. The integration map key is just the agent's local name.
The connector supplies the endpoint, credentials and shared/personal account mode. Personal
connectors still require each caller to connect their own account. Credentials never belong in
the manifest. An optional `identity: workspace` or `identity: user` can assert the expected mode;
it must match the connector.
`tools.allow` is the maximum set the agent may use. Optional `tools.approval_required` lists tools
that require confirmation for each call and must be a subset of `tools.allow`. The connector's
current permitted tools and account access can narrow this set, never expand it.
At registration, the platform records the connector ID and approved tool schemas automatically.
These platform-owned details appear separately from author configuration in the Manifest tab.
Deleting and recreating a connector with the same name does not redirect an existing version.
## GitHub repositories
An agent names the GitHub repositories it may use, and what it may do in each, under
`access.github`. Each run gets a GitHub token for exactly those repositories and capabilities;
`git` and `gh` use it automatically.
```yaml title="agent.yaml"
access:
github:
repos:
anyfind/agent-platform-website-tanomis:
can: [read, push, pull_request]
anyfind/docs:
can: [read]
```
| Capability | Lets the agent | GitHub App permission |
|---|---|---|
| `read` | clone, fetch, read files and metadata (always included) | contents: read |
| `push` | push branches | contents: write |
| `pull_request` | open and update pull requests | pull requests: write |
| `issues` | create and comment on issues | issues: write |
There is no separate merge capability: `push` and `pull_request` together can merge a pull
request unless branch protection forbids it.
A global agent cannot know a customer's repositories, so it declares a named slot instead. Each
workspace chooses the repository for the slot when it enables the agent (**Agent → Integrations**,
or `tanomis agent access NAME --bind target=owner/name`):
```yaml title="agent.yaml"
access:
github:
choose:
target:
can: [read, push, pull_request]
```
What decides access:
- **The tenant's GitHub ceiling.** A workspace admin sets which repositories agents may be given, and
how far, in **Settings → Organization → GitHub** (or `tanomis github ceiling set`). A declaration outside
the ceiling is refused at deploy or enablement with the gap named. Lowering the ceiling later
makes affected runs fail with the same reason.
- **The GitHub App installation.** A repository the workspace's connected installation cannot
see is refused at deploy.
- **Every run.** The intersection is checked again when a run starts and on every token request,
and tokens are revoked when the run ends.
Rules:
- Repository names are GitHub `owner/name`; `owner/*` is a ceiling pattern, not a declaration.
- Slot names use lowercase letters, digits, `-` and `_`.
- `github.com` and `api.github.com` are added to `network.egress` automatically. That opens
those hosts for every path: the run's token limits what the agent can do on GitHub with
platform credentials, not which GitHub repositories it can send data to.
- `asks_first` is not supported yet and is rejected rather than ignored.
- The retired top-level `repository:` block is rejected with a rewrite hint, including when an
older version that carries it is cloned or fanned out.
See [GitHub repositories](/guides/connect-tools/github-repositories) for connecting GitHub and
setting the ceiling.
## Changes and availability
- Changing the connector or allowed tools requires a new agent version.
- Rotating credentials on the same connector does not require a new version.
- Changed tool schemas require review and a new agent version.
- **Agent → Integrations** shows the declared connector, readiness and expandable allowed tools.
Operators can disable or enable access for the agent; they cannot substitute another connector.
- A required connector that is unavailable blocks execution. An unavailable optional connector is omitted.
- Disabled access remains disabled when publishing another version or migrating an old declaration.
Existing catalog-based agent versions remain compatible. New agents should use the direct
connector declaration above. No attachment creation or approval workflow is involved.
## Agent-to-agent delegation
```yaml
# caller
delegation:
calls:
- summarizer
- agent: reconciler
skills: [reconcile]
# callee
delegation:
accepts:
- tenant
skills: # optional
- name: summarize
description: Summarize a support thread and return the key facts and the customer's ask.
```
The block has two independent halves. An agent may call, be called, both, or neither, and
declaring neither is the closed default. **The manifest is the grant:** a caller names the agents
it may hand work to, a callee names who may call it, and the intersection of the two, evaluated
against the versions currently enabled for the tenant, is the authority. Nothing is created out of
band and nothing has to be redone when either side deploys a new version.
- **`calls`** — the agents this agent may delegate to. Naming a target puts the delegation tools
on this agent's bind, and it is the caller-side ceiling: an agent cannot reach a callee it did
not name. An entry is a bare agent name, or a mapping with `agent` and a `skills` list that
narrows the target to those skills. No wildcards.
- **`accepts`** — who may delegate to this agent. Empty means nobody.
| Value | Meaning |
|---|---|
| `tenant` | Any agent in the same tenant. The widest class there is. |
| `self` | Other versions of this same agent only. |
| `` | That one named agent. |
- **`skills`** — optional. A callee that declares none exposes one implicit skill, `default`,
whose input is a `prompt` plus any further fields the caller passes, described by the agent's
own `description`. Declare skills only when the agent offers several distinct typed operations.
`name` is lowercase alphanumeric with underscores or dashes and unique within the list; renaming
one breaks every caller that lists it. `description` is required and functional, not decoration:
a calling model reads it to choose a skill. `input_schema` is optional; when present it is
enforced on every call. **Schema hashes are derived by the CLI, never written by hand.**
`skills` without `accepts` is rejected: the skills would be unreachable. `accepts` without
`skills` is fine and is the common case.
## Harness settings
Keep only the block matching `framework`. Unknown keys inside a harness block are rejected at
warmup with the offending key named.
### `claude-agents-sdk`
```yaml
harness:
claude-agents-sdk: {}
```
The Claude Agent SDK harness takes no manifest options today; the empty block is the marker. Its
human-in-the-loop behavior comes from `policy.approval_required_tools`, `policy.enable_ask_user`,
and `policy.enable_claude_md`.
### `deepagents`
```yaml
harness:
deepagents:
is_async: true
stream_modes: [messages, updates, custom]
checkpointer: workspace-sqlite
backend: workspace-composite
recursion_limit: 50
```
| Key | Values | Default |
|---|---|---|
| `is_async` | boolean | `true` |
| `stream_modes` | any of `messages`, `updates`, `custom`, `values` | `[messages, updates, custom]` |
| `checkpointer` | `workspace-sqlite`, `in-memory` | `workspace-sqlite` |
| `checkpointer_options.delta_channels` | boolean | `true` |
| `store` | `workspace-sqlite`, `none` | `none` |
| `backend` | `workspace-composite`, `author-provided` | `workspace-composite` |
| `allow_agent_model_override` | boolean | `false` |
| `allow_author_checkpointer` | boolean | `false` |
| `allow_author_store` | boolean | `false` |
| `recursion_limit` | positive integer | `200` |
| `recursion_limit_ceiling` | positive integer | `1000` |
| `enable_repl_middleware` | boolean | `false` |
| `subagent_event_attribution` | `nested`, `flat` | `nested` |
`recursion_limit` must be less than or equal to `recursion_limit_ceiling`. The runtime clamps the
effective limit to the ceiling, so the ceiling is the real bound on a single run's step count.
Leave `backend`, `checkpointer`, and the three `allow_author_*` switches at their defaults unless
you are deliberately taking ownership of workspace state; the defaults are what keep the workspace
and lifecycle under the runtime's control.
## Cross-field rules
These are the checks that reject a manifest which is individually well-formed:
- New hosted versions require `persistent: true`.
- During the internal mixed-runtime rollout only, remote admission may require legacy
`cr_enabled: true` for settlement or author-declared egress. The manifest validator itself does
not make that field authoritative.
- `inherits_user_permissions: true` is illegal with `identity_mode: service`.
- A non-empty `policy.scope_ceiling` requires a non-empty `policy.allowed_audiences`.
- `integrations..tools.approval_required` must be a subset of that integration's `tools.allow`.
- `delegation.skills` requires a non-empty `delegation.accepts`.
- `harness.deepagents.recursion_limit` must not exceed `recursion_limit_ceiling`.
## Validate before you build
```bash
tanomis agent deploy . --tenant northwind-sandbox --dry-run
```
`--dry-run` runs the full preflight, including the authorization contract, and packages locally
without creating a build or uploading anything. Use it: on the remote build path, registration is
the **last** step, so a manifest error found there costs you an image that has already been built
and pushed.
---
Source: /reference/availability
# What is available where
Which capabilities are on for every tenant, which are enabled per tenant, and how to tell from the API or CLI before you build on one.
Most of Tanomis is on for every tenant. A few capabilities are enabled per tenant by a platform
operator, either because they need tenant-side configuration or because they are rolling out
shadow-first. This page is the one place that says which is which, so the guides do not have to.
- **Hosted Tanomis:** the per-tenant capabilities are enabled on request; ask us.
- **Self-hosted:** your platform operator enables them per tenant.
- Where a capability has a probe, check it in code rather than assuming.
## Capabilities
| Capability | Hosted Tanomis | Self-hosted | How to tell |
|---|---|---|---|
| Sessions, runs, streaming, replay | On | On | Always present. |
| Durable approvals and questions | On | Operator enables | A paused run shows `awaiting_input: true`; see [Approval-required actions](/guides/run/approval-required-actions). |
| Session files | On | Operator enables | `file_attachments_enabled` on [Inspect the caller](/api/workspaces/whoami), then `session_files_supported` on the session. |
| Warm starts (golden snapshots) | On | On | The deploy's final golden line. A skipped golden still deploys; sessions cold-start. |
| Automation credentials | On | On | `tanomis m2m ls`; see [Automation credentials](/guides/deploy/automation-credentials). |
| Author-declared network egress | Per tenant | Operator enables | Without it the list is validated and recorded but no external route exists. See [Restrict network egress](/guides/govern/restrict-network-egress). |
| MCP connectors and personal OAuth | Verified in SaaS development; platform enablement required | Operator enables and configures | Agent → Integrations shows connection and account readiness. Not enabled by default in every deployment. See [MCP connectors](/guides/connect-tools/mcp-connectors). |
| Manifest-declared connector access | Enabled for Acme development | Other tenants require explicit enablement | The manifest fixes the connector and allowed tools; the platform records tool schemas automatically. Operators can enable or disable access, but cannot substitute another connector. Explicit disables persist across versions. See [Declare an integration](/guides/connect-tools/integration-contracts). |
| Custom connectors | Per tenant | Operator enables | Needs the `manage` capability in your workspace. See [Create a custom connector](/guides/connect-tools/create-a-custom-connector). |
| Delegation between agents | Per deployment | Operator enables | The delegation tools are present in the bind of an agent whose manifest names a target under `delegation.calls`. See [Delegate work to another agent](/guides/run/delegate-to-another-agent). |
| Durable work and settlement | Per tenant | Operator enables | A `settlement`-mode version fails session activation where the boundary is unavailable. See [Durable work and settlement](/guides/run/durable-work-and-settlement). |
| Evaluation and the rollout gate | Per tenant, shadow-first | Operator enables | The gate records a verdict before it is allowed to block. See [Evaluation](/guides/evaluate/overview). |
| Metering and budgets | Per tenant | Operator attaches the decision point | A session shows usage as not measured where it is off. See [Usage and spend](/guides/govern/usage-and-spend). |
| Service-mode agent identity | Per tenant | Operator enables | No public probe; the session runs without an identity assertion where it is off. See [Identity and access](/concepts/identity-and-access). |
| Scoped and delegated agent authority | Held | Held | Declarations validate and record; no public runtime workflow uses them yet. |
"Per tenant" means the capability exists in the platform and a platform operator turns it on for a
tenant. "Held" means the contract is defined and the runtime path is not yet offered.
## Coming soon
Pages marked **Soon** in the sidebar describe capabilities that are being built. They carry an
under-construction notice and change without a changelog entry until they ship.
## Where to go next
- [Changelog](/changelog): when a capability moves from per tenant to on.
- [Get access](/getting-started/get-access): how to ask for a per-tenant capability on hosted
Tanomis.
---
Source: /reference/cli-configuration
# CLI configuration
Where tanomis keeps its configuration, how contexts and credentials are stored, and the environment variables and flags that override them.
`tanomis` resolves its configuration from three layers: flags on the command, environment variables,
and a config file with named contexts. Flags win, then the environment, then the file.
## The config file
`tanomis` searches from the working directory upward for `.tanomis.yaml`, then `.cog.yaml`
at each level. If no repo config exists, it uses `~/.config/tanomis/config.yaml`, falling
back to `~/.config/cog/config.yaml` when the new file is absent. `--config PATH` selects
an explicit file. Configuration and cached credentials migrate to the new directory
when each is next written; explicit file paths are respected. `tanomis login` creates a context.
```yaml title="~/.config/tanomis/config.yaml"
current-context: saas
contexts:
saas:
auth-mode: oidc
# endpoints discovered from the hosted console at login; no secret here
```
A context holds the environment's endpoints, its authentication mode, and the selected workspace.
It never holds a secret: cached credentials live separately, per context, and `tanomis logout` clears
them while keeping the context.
## Contexts
```bash title="Terminal"
tanomis config current-context
tanomis config use-context staging
tanomis config view
tanomis config delete-context old
```
`view` prints the effective configuration after flags and environment are applied, which is the
fastest way to see which environment a command will hit. Create a context by logging in to that
environment with `tanomis login --console URL --name NAME`.
## Authentication modes
| Mode | Used for |
|---|---|
| `oidc` | The hosted device-authorization login a person completes in a browser. The hosted default. |
| `none` | Environments with no authentication; not applicable to hosted Tanomis. |
For unattended use, `tanomis` exchanges an automation client's ID and secret for a token when
`TANOMIS_API_URL`, `TANOMIS_CLIENT_ID`, and `TANOMIS_CLIENT_SECRET` are set; no login mode is involved.
See [Automation credentials](/guides/deploy/automation-credentials).
For human logins, access tokens are short-lived. Before an authenticated command, `tanomis` reuses a valid token or
refreshes it; if the refresh grant is gone, the command stops and asks you to `tanomis login` again.
## Environment variables
| Variable | Effect |
|---|---|
| `TANOMIS_API_URL` | Public platform API origin for machine authentication and discovery. |
| `TANOMIS_CLIENT_ID` | Organization automation client ID. Use with `TANOMIS_CLIENT_SECRET`. |
| `TANOMIS_CLIENT_SECRET` | Automation client secret, injected from a secret manager. Never saved by machine login. |
| `TANOMIS_CA_BUNDLE` | CA bundle path for deployments with a private certificate authority. |
| `TANOMIS_TENANT` | Default for `--tenant` on tenant-scoped commands. |
| `TANOMIS_OUTPUT` | Default for `--output`: `table` or `json`. |
Each `TANOMIS_*` CLI setting accepts its former `COG_*` name as a fallback when the
new name is unset; using an old name emits one deprecation notice per process.
Other `TANOMIS_*` variables exist for internal endpoint and identity plumbing and are not part of the
public contract.
With a human login, deploy and session-run commands refuse a `--tenant` that differs from the selected workspace, so
the variable and the selection must agree. Machine authentication ignores cached human workspace
and support context; choose an organization tenant explicitly, or let the sole/default ready
tenant be selected.
## Global flags
| Flag | Effect |
|---|---|
| `--context NAME` | Use a context for this command without changing the current one. |
| `--tenant ID` | Tenant for tenant-scoped commands. Must match the selected workspace where one is selected. |
| `-o, --output table\|json` | Output format. Use `json` in scripts. |
| `--config PATH` | Config file path. |
| `--verbose` | Verbose output. |
Every command page in the [CLI reference](/cli) lists these under Global flags.
## Where to go next
- [Authenticate and select a workspace](/getting-started/authenticate-and-select-workspace)
- [Environments and contexts](/guides/deploy/environments-and-contexts)
---
Source: /reference/errors
# Errors reference
Every error code the Platform API and the CLI can return, with its HTTP status and what to do about it.
Errors are JSON with a machine-readable `error` code and usually a human-readable `message`.
Clients must tolerate a code-only response from the access gate.
```json
{
"error": "validation_error",
"message": "idempotency_key exceeds 255 bytes"
}
```
Handle by status first, then refine by code. The status tells you whether to retry; the code tells
you what to change.
## By status
| Status | Retry? | Typical codes |
|---|---|---|
| `400` | No, fix the request | `bad_request`, `validation_error`, `invalid_request`, `invalid_json`, `missing_tenant`, `invalid_tenant`, `unknown_tenant`, `workspace_not_selected`, `bad_limit`, `bad_path`, `missing_path` |
| `401` | With a new token | `unauthorized` |
| `403` | No, change the grant or operation | `forbidden`, `direct_operator_required` |
| `404` | No, check ids and capabilities | `not_found`, `no_active_bundle` |
| `405` | No | `method_not_allowed` |
| `409` | Read the resource, then decide | `conflict`, `work_has_live_run`, `upload_in_progress`, `invalid_revision`, `not_rejected`, `fact_schema_mismatch` |
| `412` | No, create a replacement | Idempotent create against a terminal session |
| `422` | No, fix the request | `validation_error`, `unprocessable`, `over_budget`, `incomparable`, `empty_dataset_version` |
| `429` | Yes, with backoff and jitter | Rate limited |
| `502` | Bounded retry | `upstream_error`, `registry_unavailable`, `account_service_unavailable` |
| `503` | Bounded retry | `unavailable` |
| `500` | Bounded retry, then report | `internal_error`, `lookup_failed`, `read_failed`, `list_failed`, `validate_failed`, `audit_unavailable` |
## By code
| Code | Meaning | What to do |
|---|---|---|
| `bad_request` | The request could not be understood. | Fix the request shape. |
| `validation_error` | A field failed validation; `message` names it. | Correct the field. Sent as `400` for request shape and `422` for semantic checks such as a manifest rule. |
| `invalid_request` | The combination of fields is not allowed, such as `work.subject` with `work.work_id`. | Send one or the other. |
| `invalid_json` | The body is not valid JSON. | Fix the encoding. |
| `missing_tenant`, `invalid_tenant`, `unknown_tenant` | No tenant resolved, or the header named one the token does not grant. | Send the workspace headers from `GET /v1/me/workspaces`. |
| `workspace_not_selected` | The CLI has no selected workspace for this context. | `tanomis workspace use ORG --tenant TENANT`. |
| `workspace_not_armed` | The selected workspace is not usable yet, typically a tenant still being provisioned. | Wait for the tenant to be `ready`. |
| `unauthorized` | Missing, expired, or invalid access token. | Obtain a new token. |
| `forbidden` | The caller cannot perform this operation in the selected workspace. | Ask an owner for the capability or grant. |
| `direct_operator_required` | The operation is reserved for a Tanomis operator acting directly. | Contact your administrator. |
| `not_found` | The resource is absent in the selected tenant, or an optional capability is unavailable here. | Check the id, the workspace, and the capability. |
| `no_active_bundle` | The tenant has no active policy bundle for this decision. | Contact your administrator. |
| `conflict` | A lifecycle or idempotency conflict: another run is active, the key matched a different body, or the session cannot admit work. | Read the resource, then retry or create anew. |
| `work_has_live_run` | The settlement work already has a live run. | Wait for it to finish. |
| `upload_in_progress` | The file's bytes are still being transferred. | Wait, then retry. |
| `invalid_revision` | The policy revision you referenced is not current. | Reload and resubmit. |
| `not_rejected` | The transition needs a rejected version and this one is not. | Check `tanomis agent statuses`. |
| `over_budget` | The evaluation run would exceed its example budget. | Lower `--max-examples`. |
| `incomparable` | The two experiments do not share a dataset version. | Compare experiments over the same dataset version. |
| `empty_dataset_version` | The dataset version has no examples. | Populate it before running. |
| `unavailable` | The capability or a dependency is temporarily unavailable. | Retry with backoff. |
| `upstream_error`, `registry_unavailable`, `account_service_unavailable` | A platform dependency failed. | Retry boundedly; report if it persists. |
| `internal_error` and other `*_failed` codes | An unexpected platform error. | Retry boundedly, then contact support with the request id. |
## CLI
The CLI prints the server's code and message as returned and exits non-zero. It forms no
authorization opinion of its own: a refusal you see at the terminal is the server's, so a rule
change never leaves a stale client refusing correct calls. Local refusals are limited to missing
arguments and to irreversible commands that require `--yes`.
## Where to go next
- [Platform API overview](/api#errors-and-retries): the retry contract per status.
- [Sessions and runs](/concepts/sessions-and-runs): the conflicts you can hit on create and run.
---
Source: /reference/events
# Events reference
The envelope every run event shares, the event types a consumer can rely on, and the payload of each.
Run events are how your application watches a run. They arrive as server-sent events on
`GET /v1/sessions/{session_id}/runs/{run_id}/events`, each frame carrying one JSON event.
- Every event has the same envelope: `id`, `type`, `session_id`, `run_id`, `created_at`, `data`.
- Three types are **terminal**: `run_completed`, `run_cancelled`, `error`.
- Two types mean **a person is needed**: `permission_request` and `user_question`.
- Everything else is progress. Ignore unknown types and unknown fields; they are forward
compatible and non-terminal unless this page names them.
## The envelope
```text
id: 14
event: message_delta
data: {"id":"14","type":"message_delta","session_id":"ses_7d4f9c","run_id":"run_a12bc3","created_at":"2026-09-03T09:00:00.014Z","data":{"text":"I found"}}
```
| Field | Meaning |
|---|---|
| `id` | Orders events within this run only. Opaque; store it with the session and run ids, and send it back as `Last-Event-ID` to resume. |
| `type` | The event type below. |
| `session_id`, `run_id` | The run the event belongs to. Deduplicate by run id plus event id. |
| `created_at` | RFC 3339 UTC. |
| `data` | The type-specific payload. |
## Lifecycle events
| Type | Terminal | Payload | Meaning |
|---|---|---|---|
| `run_started` | No | `run_id` | The run was dispatched to the agent. |
| `run_completed` | **Yes** | `run_id`, optional `recovered` | The run finished. Read the run resource for its authoritative status. |
| `run_cancelled` | **Yes** | `run_id`, optional `recovered` | The run ended after cancellation. |
| `error` | **Yes** | `message`, and an error code where one applies | The run ended with an error. The run resource carries `error_code` and `error_message`. |
`recovered: true` marks a terminal event emitted while the platform reconciled a run after a
restart, rather than by the agent process itself. Treat it exactly like the non-recovered form.
## Output events
| Type | Payload | Meaning |
|---|---|---|
| `message_delta` | `text`, `framework`, `model` | A chunk of the agent's response. Concatenate deltas in id order. |
Harness-specific events can appear alongside these, with their own payloads. Render what you
recognise and ignore the rest.
## Interaction events
| Type | Payload | Meaning |
|---|---|---|
| `permission_request` | `request_id`, `interaction_key`, `tool_name`, `tool_input`, optional `replayed` | The agent wants to run a tool listed in `approval_required_tools` and is waiting for a decision. |
| `permission_resolved` | `interaction_key` and the decision | A decision was recorded. |
| `user_question` | `request_id`, `interaction_key`, the question, optional `replayed` | The agent asked a person a question and is waiting. |
| `user_answered` | `interaction_key` and the answer | An answer was recorded. |
Answer by `interaction_key`, never by `request_id`, and never from the event alone: list the
session's pending interactions to get the durable record, then answer it. `replayed: true` marks an
interaction re-emitted after a reconnect or a restored runtime; it is the same interaction, not a
new one. See [Approval-required actions](/guides/run/approval-required-actions).
## Context events
| Type | Meaning |
|---|---|
| `context_file_loaded`, `context_file_changed` | A context file was read into the agent's context, or changed since. |
| `claude_md_loaded`, `claude_md_changed` | The same, for a project `CLAUDE.md` when `enable_claude_md` is on. |
| `agent_skills_loaded` | The agent's `skills/` directory was mirrored into the session workspace. Names the skills that loaded, and warns about directories that could not. |
## Rules a consumer can rely on
1. Ids order events within one run and are opaque strings. Never compare ids across runs.
2. Replay after `Last-Event-ID` starts strictly after that id when history is available. Store the
id after processing the event, and deduplicate by run id plus event id.
3. A stream can close cleanly without a terminal event. Read the run resource, and reconnect only
while it is non-terminal.
4. The three terminal names above are the contract. Harness-specific catalogs and payload fields
can evolve; the envelope, the cursor behaviour, and the terminal names do not.
## Where to go next
- [Stream and replay events](/guides/run/stream-and-replay-events): the consumer loop with
cursor storage and deduplication.
- [Stream run events](/api/runs/stream-run-events): the endpoint.
---
Source: /reference/glossary
# Glossary
The words Tanomis uses, each defined once, with a link to the page that explains it.
| Term | Meaning |
|---|---|
| **Agent** | A stable name that owns a lineage of versions. [Agents and versions](/concepts/agents-and-versions) |
| **Automation client** | An organization-owned machine identity for CI or a backend, with a permission profile; also called an M2M client. [Automation credentials](/guides/deploy/automation-credentials) |
| **Assessment** | A scorer's verdict on one execution trace. [Evaluation overview](/guides/evaluate/overview) |
| **Audit ledger** | The tenant-isolated, append-only, hash-chained, signed record of decisions and attribution. It holds references and hashes, never content. [Audit and evidence](/guides/audit/audit-and-evidence) |
| **Canary** | A version a tenant serves at a percentage share alongside its baseline. [Roll out and roll back](/guides/deploy/rollout-and-rollback) |
| **Ceiling** | A declared or administered upper bound that only ever narrows: models, external hosts, connector tools, agent authority. [Policy, approvals, and evidence](/concepts/governance) |
| **Connection** | A configured workspace service connection; personal integrations additionally require each user's own account. [MCP connectors](/guides/connect-tools/mcp-connectors) |
| **Connector** | A governed MCP endpoint an administrator defines, with a discovered tool inventory. [MCP connectors](/guides/connect-tools/mcp-connectors) |
| **Context** | Local CLI configuration for one Tanomis environment and its login. [CLI configuration](/reference/cli-configuration) |
| **Delegation** | One agent handing a skill invocation to another with narrowed authority. [Delegate work to another agent](/guides/run/delegate-to-another-agent) |
| **Enablement** | The tenant-scoped assignment that lets a version receive traffic. [Agents and versions](/concepts/agents-and-versions) |
| **Execution trace** | One OpenTelemetry trace per run: model calls, tool calls, timing, and captured content. [Capture execution traces](/guides/evaluate/capture-traces) |
| **Experiment** | A candidate version replayed over a dataset version, with aggregate metrics. [Evaluation overview](/guides/evaluate/overview) |
| **Global agent** | An agent registered once at platform level and enabled per tenant, each tenant on the version you choose. [Platform model](/concepts/platform-model) |
| **Golden snapshot** | A warm-process checkpoint of an agent image over an empty workspace, restored to start sessions in under a second. [Agents and versions](/concepts/agents-and-versions) |
| **Harness** | The framework an agent is written against: Claude Agent SDK or Deep Agents. [Choose a harness](/guides/build/choose-a-harness) |
| **Idempotency key** | A caller-chosen key that makes a retry return the original session or run. [Sessions and runs](/concepts/sessions-and-runs) |
| **Interaction** | A durable question or permission request a run pauses on until a person answers. [Approval-required actions](/guides/run/approval-required-actions) |
| **Invoking principal** | The person or service that started a request, resolved from the access token. [Identity and access](/concepts/identity-and-access) |
| **LLM gateway** | The per-tenant gateway every model call passes through: authorization, metering, tracing. [Choose models](/guides/build/choose-models) |
| **Manifest** | `agent.yaml`, the version's declared contract. [Agent manifest](/reference/agent-manifest) |
| **Organization** | The customer account: members, roles, invitations, tenant attachments. [Organizations and tenants](/concepts/organizations-and-tenants) |
| **Platform operator** | Whoever runs the Tanomis deployment: Tanomis staff in the hosted service, your own team when self-hosted. [Platform model](/concepts/platform-model) |
| **Run** | One admitted unit of work inside a session; its resource is the authoritative status. [Sessions and runs](/concepts/sessions-and-runs) |
| **Scope ceiling** | The most authority an agent's own tokens can ever carry. [Set the authorization ceiling](/guides/govern/set-the-authorization-ceiling) |
| **Sandbox tenant** | A tenant that serves no customer, where developers and CI test before a rollout reaches customers. [Environments and contexts](/guides/deploy/environments-and-contexts) |
| **Session** | The continuity boundary for one enabled agent version: workspace, runs, files, interactions. [Sessions and runs](/concepts/sessions-and-runs) |
| **Skill** | A unit of work an agent accepts from another agent. [Delegate work to another agent](/guides/run/delegate-to-another-agent) |
| **Tenant** | One of your customers: an isolated runtime with its own policy, connectors, and data. [Organizations and tenants](/concepts/organizations-and-tenants) |
| **Version** | One immutable build of an agent with its contract captured at registration. [Versions and statuses](/guides/deploy/versions-and-statuses) |
| **Warm start** | Starting a session from a golden snapshot instead of booting the harness cold. [Agents and versions](/concepts/agents-and-versions) |
| **Workspace** | Your view of one organization and one tenant, with your role and capabilities. [Organizations and tenants](/concepts/organizations-and-tenants) |
| **Integration** | An agent manifest declaration naming a configured connector and the tools that version may use. The connector supplies the endpoint and account mode. [MCP connectors](/guides/connect-tools/mcp-connectors) |
---
Source: /reference/limits
# Limits and quotas
Request, session, file, and rate limits, with the status you see when you hit one.
Every limit an application can hit, in one table. The numbers are being confirmed for publication.
This page is planned and not yet written, because the capability it describes is not yet
generally available. The structure is here so you can see where it fits; the notice comes off
when it ships.
## What this page will cover
- Idempotency key and payload sizes.
- Session lifetime and idle limits.
- File size and count limits.
- Rate limits and the 429 contract.
## Meanwhile
- The [Platform API overview](/api#errors-and-retries) already describes the retry contract for each status.
---
Source: /reference/organization-management-api
# Organization management API
Human owner/admin HTTP endpoints for machine client credentials and tenant lifecycle.
These endpoints manage your organization's automation clients and tenants. Use a **human owner
or admin** bearer token and the public platform API origin. An automation client cannot call them,
even to rotate its own credentials. The organization ID comes from the path; no workspace
selection is needed for these organization-scoped operations.
For interactive administration, the [Console and M2M CLI](/guides/deploy/automation-credentials)
and [tenant commands](/guides/manage/tenants-and-environments) provide the same operations.
## Machine clients
All paths below are relative to `/v1/organizations/{organization_id}/m2m`.
`{client_id}` is the platform record ID (`client.id`), not the OAuth `client_id` returned for
credential use. `{credential_id}` is a credential metadata ID.
| Method | Path | Result |
|---|---|---|
| GET | base path | List `{clients: [...]}` without secrets. |
| GET | `/profiles` | Supported permission profiles, scopes, and catalog version. |
| POST | base path | Create a client and issue its first credential. |
| GET | `/{client_id}` | Inspect `{client, credentials}` without secrets. |
| PATCH | `/{client_id}` | Rename, change permissions, disable, or enable. |
| POST | `/{client_id}/credentials` | Add a credential for rotation. |
| DELETE | `/{client_id}/credentials/{credential_id}` | Revoke one credential. |
| DELETE | `/{client_id}` | Delete the client and withdraw access. |
### Create
Send `Content-Type: application/json` and an `Idempotency-Key` of 8–256 characters:
```json
{
"name": "production-deployer",
"profile": "deploy",
"expires_in_days": 90
}
```
`description` is optional. Use `observe`, `invoke`, or `deploy`, or set `profile` to `custom` with an array of supported
`scopes` instead of a named profile. The default credential lifetime is 90 days and maximum 365.
A successful issue returns HTTP 201 with `{client, credential, client_id, client_secret}`. The
secret is returned once and cannot be read later. Retrying with the same idempotency key cannot
issue another secret; an already completed issue returns a conflict without disclosing it again.
Do not log or cache the response. Inspect metadata and rotate if a response was lost.
### Update
Fetch the client and send its current `version` with a PATCH:
```json
{"version": 3, "name": "release-deployer", "profile": "deploy"}
```
To disable, send `{"version": 3, "status": "disabled"}` instead. Use `active` to re-enable.
Do not combine a status transition with name or permission changes. A stale version conflicts;
fetch the record again before deciding whether to retry.
### Rotate and revoke
Credential creation also requires an `Idempotency-Key` and a non-empty label:
```json
{"label": "September rotation", "expires_in_days": 90}
```
At most two live, unexpired credentials are allowed per client. Switch consumers to the new
secret, then DELETE the old credential. At most 20 non-deleted clients are allowed per organization.
Updates can return 200 or 202; revocation and deletion can return 204 or 202. A 202 means cleanup
or application of a change is still pending. Inspect the resource for progress. Client disable/delete and scope removal
apply at the next platform authorization decision without waiting for token expiry. Revoking an
individual credential prevents new token issuance once provider cleanup completes; already-issued
tokens can remain valid until expiry. Disable the client if immediate withdrawal is required.
Machine management responses use `Cache-Control: no-store`. Mutations are rate-limited per
organization and human actor; handle HTTP 429 with backoff. A conflict can indicate a changed
version, lifecycle state, credential limit, or a completed one-time issue; inspect before retrying.
## Tenants
Paths are relative to `/v1/organizations/{organization_id}/tenants`.
| Method | Path | Operation |
|---|---|---|
| GET | base path | List organization tenants. |
| POST | base path | Create a tenant. |
| GET | `/{tenant_id}` | Inspect lifecycle and attachment state. |
| POST | `/{tenant_id}:retry` | Retry provisioning. |
| POST | `/{tenant_id}:offboard` | Withdraw access and start irreversible cleanup. |
Create with an `Idempotency-Key` and JSON body:
```json
{"display_name": "bluewater-bank", "mode": "saas"}
```
The deployment controls supported modes and quotas. Creation returns 202 while provisioning runs.
A tenant becomes usable only when `status` is `active` and attachment `state` is `ready`.
Organization machine clients gain reach automatically at that point.
Offboard with `{"confirm": ""}`. It returns 202, withdraws machine reach
immediately, and cleans up asynchronously. Preserve required data before confirming.
For runtime calls made **by** a machine client, see [Authenticate a backend](/guides/run/authenticate-your-backend)
and the [application API reference](/api).